1#!/usr/bin/env python2 2# Copyright 2019 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 6from __future__ import print_function 7 8import argparse 9import contextlib 10import copy 11import logging 12import os 13import re 14import shutil 15import stat 16import subprocess 17import tempfile 18import textwrap 19import zipfile 20# Use 'sudo pip install jinja2' to install. 21from jinja2 import Template 22 23 24# TODO(ihf): Assign better TIME to control files. Scheduling uses this to run 25# LENGTHY first, then LONG, MEDIUM etc. But we need LENGTHY for the collect 26# job, downgrade all others. Make sure this still works in CQ/smoke suite. 27_CONTROLFILE_TEMPLATE = Template( 28 textwrap.dedent("""\ 29 # Copyright {{year}} The Chromium OS Authors. All rights reserved. 30 # Use of this source code is governed by a BSD-style license that can be 31 # found in the LICENSE file. 32 33 # This file has been automatically generated. Do not edit! 34 {%- if servo_support_needed %} 35 36 from autotest_lib.server import utils 37 38 {%- endif %} 39 40 AUTHOR = 'ARC++ Team' 41 NAME = '{{name}}' 42 ATTRIBUTES = '{{attributes}}' 43 DEPENDENCIES = '{{dependencies}}' 44 JOB_RETRIES = {{job_retries}} 45 TEST_TYPE = 'server' 46 TIME = '{{test_length}}' 47 MAX_RESULT_SIZE_KB = {{max_result_size_kb}} 48 {%- if sync_count and sync_count > 1 %} 49 SYNC_COUNT = {{sync_count}} 50 {%- endif %} 51 {%- if priority %} 52 PRIORITY = {{priority}} 53 {%- endif %} 54 DOC = '{{DOC}}' 55 {%- if servo_support_needed %} 56 57 # For local debugging, if your test setup doesn't have servo, REMOVE these 58 # two lines. 59 args_dict = utils.args_to_dict(args) 60 servo_args = hosts.CrosHost.get_servo_arguments(args_dict) 61 62 {%- endif %} 63 {% if sync_count and sync_count > 1 %} 64 from autotest_lib.server import utils as server_utils 65 def {{test_func_name}}(ntuples): 66 host_list = [hosts.create_host(machine) for machine in ntuples] 67 {% else %} 68 def {{test_func_name}}(machine): 69 {%- if servo_support_needed %} 70 # REMOVE 'servo_args=servo_args' arg for local debugging if your test 71 # setup doesn't have servo. 72 try: 73 host_list = [hosts.create_host(machine, servo_args=servo_args)] 74 except: 75 # Just ignore any servo setup flakiness. 76 host_list = [hosts.create_host(machine)] 77 {%- else %} 78 host_list = [hosts.create_host(machine)] 79 {%- endif %} 80 {%- endif %} 81 job.run_test( 82 '{{base_name}}', 83 {%- if camera_facing %} 84 camera_facing='{{camera_facing}}', 85 cmdline_args=args, 86 {%- endif %} 87 hosts=host_list, 88 iterations=1, 89 {%- if max_retries != None %} 90 max_retry={{max_retries}}, 91 {%- endif %} 92 {%- if enable_default_apps %} 93 enable_default_apps=True, 94 {%- endif %} 95 {%- if needs_push_media %} 96 needs_push_media={{needs_push_media}}, 97 {%- endif %} 98 tag='{{tag}}', 99 test_name='{{name}}', 100 {%- if authkey %} 101 authkey='{{authkey}}', 102 {%- endif %} 103 run_template={{run_template}}, 104 retry_template={{retry_template}}, 105 target_module={% if target_module %}'{{target_module}}'{% else %}None{%endif%}, 106 target_plan={% if target_plan %}'{{target_plan}}'{% else %}None{% endif %}, 107 {%- if abi %} 108 bundle='{{abi}}', 109 {%- endif %} 110 {%- if extra_artifacts %} 111 extra_artifacts={{extra_artifacts}}, 112 {%- endif %} 113 {%- if extra_artifacts_host %} 114 extra_artifacts_host={{extra_artifacts_host}}, 115 {%- endif %} 116 {%- if uri %} 117 uri='{{uri}}', 118 {%- endif %} 119 {%- for arg in extra_args %} 120 {{arg}}, 121 {%- endfor %} 122 {%- if servo_support_needed %} 123 hard_reboot_on_failure=True, 124 {%- endif %} 125 {%- if camera_facing %} 126 load_waivers=False, 127 {%- endif %} 128 timeout={{timeout}}) 129 130 {% if sync_count and sync_count > 1 -%} 131 ntuples, failures = server_utils.form_ntuples_from_machines(machines, 132 SYNC_COUNT) 133 # Use log=False in parallel_simple to avoid an exception in setting up 134 # the incremental parser when SYNC_COUNT > 1. 135 parallel_simple({{test_func_name}}, ntuples, log=False) 136 {% else -%} 137 parallel_simple({{test_func_name}}, machines) 138 {% endif %} 139""")) 140 141CONFIG = None 142 143_COLLECT = 'tradefed-run-collect-tests-only-internal' 144_PUBLIC_COLLECT = 'tradefed-run-collect-tests-only' 145 146_TEST_LENGTH = {1: 'FAST', 2: 'SHORT', 3: 'MEDIUM', 4: 'LONG', 5: 'LENGTHY'} 147 148_ALL = 'all' 149 150 151def get_tradefed_build(line): 152 """Gets the build of Android CTS from tradefed. 153 154 @param line Tradefed identification output on startup. Example: 155 Android Compatibility Test Suite 7.0 (3423912) 156 @return Tradefed CTS build. Example: 2813453. 157 """ 158 # Sample string: 159 # - Android Compatibility Test Suite 7.0 (3423912) 160 # - Android Compatibility Test Suite for Instant Apps 1.0 (4898911) 161 # - Android Google Mobile Services (GMS) Test Suite 6.0_r1 (4756896) 162 m = re.search(r' \((.*)\)', line) 163 if m: 164 return m.group(1) 165 logging.warning('Could not identify build in line "%s".', line) 166 return '<unknown>' 167 168 169def get_tradefed_revision(line): 170 """Gets the revision of Android CTS from tradefed. 171 172 @param line Tradefed identification output on startup. 173 Example: 174 Android Compatibility Test Suite 6.0_r6 (2813453) 175 Android Compatibility Test Suite for Instant Apps 1.0 (4898911) 176 @return Tradefed CTS revision. Example: 6.0_r6. 177 """ 178 tradefed_identifier_list = [ 179 r'Android Google Mobile Services \(GMS\) Test Suite (.*) \(', 180 r'Android Compatibility Test Suite(?: for Instant Apps)? (.*) \(', 181 r'Android Vendor Test Suite (.*) \(', 182 r'Android Security Test Suite (.*) \(' 183 ] 184 185 for identifier in tradefed_identifier_list: 186 m = re.search(identifier, line) 187 if m: 188 return m.group(1) 189 190 logging.warning('Could not identify revision in line "%s".', line) 191 return None 192 193 194def get_bundle_abi(filename): 195 """Makes an educated guess about the ABI. 196 197 In this case we chose to guess by filename, but we could also parse the 198 xml files in the module. (Maybe this needs to be done in the future.) 199 """ 200 if CONFIG.get('DYNAMIC_TEST_FETCH'): 201 return None 202 if filename.endswith('arm.zip'): 203 return 'arm' 204 if filename.endswith('arm64.zip'): 205 return 'arm64' 206 if filename.endswith('x86.zip'): 207 return 'x86' 208 209 assert(CONFIG['TRADEFED_CTS_COMMAND'] =='gts'), 'Only GTS has empty ABI' 210 return '' 211 212 213def get_extension(module, abi, revision, is_public=False, led_provision=None, camera_facing=None): 214 """Defines a unique string. 215 216 Notice we chose module revision first, then abi, as the module revision 217 changes regularly. This ordering makes it simpler to add/remove modules. 218 @param module: CTS module which will be tested in the control file. If 'all' 219 is specified, the control file will runs all the tests. 220 @param public: boolean variable to specify whether or not the bundle is from 221 public source or not. 222 @param led_provision: string or None indicate whether the camerabox has led 223 light or not. 224 @param camera_facing: string or None indicate whether it's camerabox tests 225 for specific camera facing or not. 226 @return string: unique string for specific tests. If public=True then the 227 string is "<abi>.<module>", otherwise, the unique string is 228 "<revision>.<abi>.<module>". Note that if abi is empty, the 229 abi part is omitted. 230 """ 231 ext_parts = [] 232 if not CONFIG.get('DYNAMIC_TEST_FETCH') and not is_public: 233 ext_parts = [revision] 234 if not CONFIG.get('DYNAMIC_TEST_FETCH') and abi: 235 ext_parts += [abi] 236 ext_parts += [module] 237 if led_provision: 238 ext_parts += [led_provision] 239 if camera_facing: 240 ext_parts += ['camerabox', camera_facing] 241 return '.'.join(ext_parts) 242 243 244def get_doc(modules, abi, is_public): 245 """Defines the control file DOC string.""" 246 if modules.intersection(get_collect_modules(is_public)): 247 module_text = 'all' 248 else: 249 # Generate per-module DOC 250 module_text = 'module ' + ', '.join(sorted(list(modules))) 251 252 abi_text = (' using %s ABI' % abi) if abi else '' 253 254 doc = ('Run %s of the %s%s in the ARC++ container.' 255 % (module_text, CONFIG['DOC_TITLE'], abi_text)) 256 return doc 257 258 259def servo_support_needed(modules, is_public=True): 260 """Determines if servo support is needed for a module.""" 261 return not is_public and all(module in CONFIG['NEEDS_POWER_CYCLE'] 262 for module in modules) 263 264 265def get_controlfile_name(module, 266 abi, 267 revision, 268 is_public=False, 269 led_provision=None, 270 camera_facing=None): 271 """Defines the control file name. 272 273 @param module: CTS module which will be tested in the control file. If 'all' 274 is specified, the control file will runs all the tests. 275 @param public: boolean variable to specify whether or not the bundle is from 276 public source or not. 277 @param camera_facing: string or None indicate whether it's camerabox tests 278 for specific camera facing or not. 279 @param led_provision: string or None indicate whether the camerabox has led 280 light or not. 281 @return string: control file for specific tests. If public=True or 282 module=all, then the name will be "control.<abi>.<module>", 283 otherwise, the name will be 284 "control.<revision>.<abi>.<module>". 285 """ 286 return 'control.%s' % get_extension(module, abi, revision, is_public, led_provision, 287 camera_facing) 288 289 290def get_sync_count(_modules, _abi, _is_public): 291 return 1 292 293 294def get_suites(modules, abi, is_public, camera_facing=None): 295 """Defines the suites associated with a module. 296 297 @param module: CTS module which will be tested in the control file. If 'all' 298 is specified, the control file will runs all the tests. 299 # TODO(ihf): Make this work with the "all" and "collect" generation, 300 # which currently bypass this function. 301 """ 302 if is_public: 303 # On moblab everything runs in the same suite. 304 return [CONFIG['MOBLAB_SUITE_NAME']] 305 306 suites = set(CONFIG['INTERNAL_SUITE_NAMES']) 307 308 for module in modules: 309 if module in get_collect_modules(is_public): 310 # We collect all tests both in arc-gts and arc-gts-qual as both have 311 # a chance to be complete (and used for submission). 312 suites |= set(CONFIG['QUAL_SUITE_NAMES']) 313 if module in CONFIG['EXTRA_ATTRIBUTES']: 314 # Special cases come with their own suite definitions. 315 suites |= set(CONFIG['EXTRA_ATTRIBUTES'][module]) 316 if module in CONFIG['SMOKE'] and (abi == 'arm' or abi == ''): 317 # Handle VMTest by adding a few jobs to suite:smoke. 318 suites.add('suite:smoke') 319 if module in CONFIG['HARDWARE_DEPENDENT_MODULES']: 320 # CTS modules to be run on all unibuild models. 321 suites.add('suite:arc-cts-unibuild-hw') 322 if abi == 'x86': 323 # Handle a special builder for running all of CTS in a betty VM. 324 # TODO(ihf): figure out if this builder is still alive/needed. 325 vm_suite = None 326 for suite in CONFIG['VMTEST_INFO_SUITES']: 327 if not vm_suite: 328 vm_suite = suite 329 if module in CONFIG['VMTEST_INFO_SUITES'][suite]: 330 vm_suite = suite 331 if vm_suite is not None: 332 suites.add('suite:%s' % vm_suite) 333 # One or two modules hould be in suite:bvt-arc to cover CQ/PFQ. A few 334 # spare/fast modules can run in suite:bvt-perbuild in case we need a 335 # replacement for the module in suite:bvt-arc (integration test for 336 # cheets_CTS only, not a correctness test for CTS content). 337 if module in CONFIG['BVT_ARC'] and (abi == 'arm' or abi == ''): 338 suites.add('suite:bvt-arc') 339 elif module in CONFIG['BVT_PERBUILD'] and (abi == 'arm' or abi == ''): 340 suites.add('suite:bvt-perbuild') 341 342 if camera_facing != None: 343 suites.add('suite:arc-cts-camera') 344 345 return sorted(list(suites)) 346 347 348def get_dependencies(modules, abi, is_public, led_provision, camera_facing): 349 """Defines lab dependencies needed to schedule a module. 350 351 @param module: CTS module which will be tested in the control file. If 'all' 352 is specified, the control file will runs all the tests. 353 @param abi: string that specifies the application binary interface of the 354 current test. 355 @param is_public: boolean variable to specify whether or not the bundle is 356 from public source or not. 357 @param led_provision: specify if led is provisioned in the camerabox setup. 'noled' when 358 there is no led light in the box and 'led' otherwise. 359 @param camera_facing: specify requirement of camerabox setup with target 360 test camera facing. Set to None if it's not camerabox 361 related test. 362 """ 363 dependencies = ['arc'] 364 if abi in CONFIG['LAB_DEPENDENCY']: 365 dependencies += CONFIG['LAB_DEPENDENCY'][abi] 366 367 if led_provision is not None: 368 dependencies.append('camerabox_light:'+led_provision) 369 370 if camera_facing is not None: 371 dependencies.append('camerabox_facing:'+camera_facing) 372 373 for module in modules: 374 if is_public and module in CONFIG['PUBLIC_DEPENDENCIES']: 375 dependencies.extend(CONFIG['PUBLIC_DEPENDENCIES'][module]) 376 377 return ', '.join(dependencies) 378 379 380def get_job_retries(modules, is_public, suites): 381 """Define the number of job retries associated with a module. 382 383 @param module: CTS module which will be tested in the control file. If a 384 special module is specified, the control file will runs all 385 the tests without retry. 386 @param is_public: true if the control file is for moblab (public) use. 387 @param suites: the list of suites that the control file belongs to. 388 """ 389 # TODO(haddowk): remove this when cts p has stabalized. 390 if is_public: 391 return CONFIG['CTS_JOB_RETRIES_IN_PUBLIC'] 392 # Presubmit check forces to set 2 or more retries for CQ tests. 393 if 'suite:bvt-arc' in suites: 394 return 2 395 retries = 1 # 0 is NO job retries, 1 is one retry etc. 396 for module in modules: 397 # We don't want job retries for module collection or special cases. 398 if (module in get_collect_modules(is_public) or module == _ALL or 399 ('CtsDeqpTestCases' in CONFIG['EXTRA_MODULES'] and 400 module in CONFIG['EXTRA_MODULES']['CtsDeqpTestCases']['SUBMODULES'] 401 )): 402 retries = 0 403 return retries 404 405 406def get_max_retries(modules, abi, suites, is_public): 407 """Partners experiance issues where some modules are flaky and require more 408 409 retries. Calculate the retry number per module on moblab. 410 @param module: CTS module which will be tested in the control file. 411 """ 412 retry = -1 413 if is_public: 414 if _ALL in CONFIG['PUBLIC_MODULE_RETRY_COUNT']: 415 retry = CONFIG['PUBLIC_MODULE_RETRY_COUNT'][_ALL] 416 417 # In moblab at partners we may need many more retries than in lab. 418 for module in modules: 419 if module in CONFIG['PUBLIC_MODULE_RETRY_COUNT']: 420 retry = max(retry, CONFIG['PUBLIC_MODULE_RETRY_COUNT'][module]) 421 else: 422 # See if we have any special values for the module, chose the largest. 423 for module in modules: 424 if module in CONFIG['CTS_MAX_RETRIES']: 425 retry = max(retry, CONFIG['CTS_MAX_RETRIES'][module]) 426 427 # Ugly overrides. 428 # In bvt we don't want to hold the CQ/PFQ too long. 429 if 'suite:bvt-arc' in suites: 430 retry = 3 431 # Not strict as CQ for bvt-perbuild. Let per-module config take priority. 432 if retry == -1 and 'suite:bvt-perbuild' in suites: 433 retry = 3 434 # During qualification we want at least 9 retries, possibly more. 435 # TODO(kinaba&yoshiki): do not abuse suite names 436 if CONFIG.get('QUAL_SUITE_NAMES') and \ 437 set(CONFIG['QUAL_SUITE_NAMES']) & set(suites): 438 retry = max(retry, CONFIG['CTS_QUAL_RETRIES']) 439 # Collection should never have a retry. This needs to be last. 440 if modules.intersection(get_collect_modules(is_public)): 441 retry = 0 442 443 if retry >= 0: 444 return retry 445 # Default case omits the retries in the control file, so tradefed_test.py 446 # can chose its own value. 447 return None 448 449 450def get_max_result_size_kb(modules, is_public): 451 """Returns the maximum expected result size in kB for autotest. 452 453 @param modules: List of CTS modules to be tested by the control file. 454 """ 455 for module in modules: 456 if (module in get_collect_modules(is_public) or 457 module == 'CtsDeqpTestCases'): 458 # CTS tests and dump logs for android-cts. 459 return CONFIG['LARGE_MAX_RESULT_SIZE'] 460 # Individual module normal produces less results than all modules. 461 return CONFIG['NORMAL_MAX_RESULT_SIZE'] 462 463 464def get_extra_args(modules, is_public): 465 """Generate a list of extra arguments to pass to the test. 466 467 Some params are specific to a particular module, particular mode or 468 combination of both, generate a list of arguments to pass into the template. 469 470 @param modules: List of CTS modules to be tested by the control file. 471 """ 472 extra_args = set() 473 preconditions = [] 474 login_preconditions = [] 475 prerequisites = [] 476 for module in modules: 477 # Remove this once JDK9 is the base JDK for lab. 478 if CONFIG.get('USE_JDK9', False): 479 extra_args.add('use_jdk9=True') 480 if is_public: 481 extra_args.add('warn_on_test_retry=False') 482 extra_args.add('retry_manual_tests=True') 483 preconditions.extend(CONFIG['PUBLIC_PRECONDITION'].get(module, [])) 484 else: 485 preconditions.extend(CONFIG['PRECONDITION'].get(module, [])) 486 login_preconditions.extend( 487 CONFIG['LOGIN_PRECONDITION'].get(module, [])) 488 prerequisites.extend(CONFIG['PREREQUISITES'].get(module,[])) 489 490 # Notice: we are just squishing the preconditions for all modules together 491 # with duplicated command removed. This may not always be correct. 492 # In such a case one should split the bookmarks in a way that the modules 493 # with conflicting preconditions end up in separate control files. 494 def deduped(lst): 495 """Keep only the first occurrence of each element.""" 496 return [e for i, e in enumerate(lst) if e not in lst[0:i]] 497 498 if preconditions: 499 # To properly escape the public preconditions we need to format the list 500 # manually using join. 501 extra_args.add('precondition_commands=[%s]' % ', '.join( 502 deduped(preconditions))) 503 if login_preconditions: 504 extra_args.add('login_precondition_commands=[%s]' % ', '.join( 505 deduped(login_preconditions))) 506 if prerequisites: 507 extra_args.add("prerequisites=['%s']" % "', '".join( 508 deduped(prerequisites))) 509 return sorted(list(extra_args)) 510 511 512def get_test_length(modules): 513 """ Calculate the test length based on the module name. 514 515 To better optimize DUT's connected to moblab, it is better to run the 516 longest tests and tests that require limited resources. For these modules 517 override from the default test length. 518 519 @param module: CTS module which will be tested in the control file. If 'all' 520 is specified, the control file will runs all the tests. 521 522 @return string: one of the specified test lengths: 523 ['FAST', 'SHORT', 'MEDIUM', 'LONG', 'LENGTHY'] 524 """ 525 length = 3 # 'MEDIUM' 526 for module in modules: 527 if module in CONFIG['OVERRIDE_TEST_LENGTH']: 528 length = max(length, CONFIG['OVERRIDE_TEST_LENGTH'][module]) 529 return _TEST_LENGTH[length] 530 531 532def get_test_priority(modules, is_public): 533 """ Calculate the test priority based on the module name. 534 535 On moblab run all long running tests and tests that have some unique 536 characteristic at a higher priority (50). 537 538 This optimizes the total run time of the suite assuring the shortest 539 time between suite kick off and 100% complete. 540 541 @param module: CTS module which will be tested in the control file. 542 543 @return int: 0 if priority not to be overridden, or priority number otherwise. 544 """ 545 if not is_public: 546 return 0 547 548 priority = 0 549 overide_test_priority_dict = CONFIG.get('PUBLIC_OVERRIDE_TEST_PRIORITY', {}) 550 for module in modules: 551 if module in overide_test_priority_dict: 552 priority = max(priority, overide_test_priority_dict[module]) 553 elif (module in CONFIG['OVERRIDE_TEST_LENGTH'] or 554 module in CONFIG['PUBLIC_DEPENDENCIES'] or 555 module in CONFIG['PUBLIC_PRECONDITION'] or 556 module.split('.')[0] in CONFIG['OVERRIDE_TEST_LENGTH']): 557 priority = max(priority, 50) 558 return priority 559 560 561def get_authkey(is_public): 562 if is_public or not CONFIG['AUTHKEY']: 563 return None 564 return CONFIG['AUTHKEY'] 565 566 567def _format_collect_cmd(is_public, abi_to_run, retry): 568 """Returns a list specifying tokens for tradefed to list all tests.""" 569 if retry: 570 return None 571 cmd = ['run', 'commandAndExit', 'collect-tests-only'] 572 if CONFIG['TRADEFED_DISABLE_REBOOT_ON_COLLECTION']: 573 cmd += ['--disable-reboot'] 574 for m in CONFIG['MEDIA_MODULES']: 575 cmd.append('--module-arg') 576 cmd.append('%s:skip-media-download:true' % m) 577 if (not is_public and 578 not CONFIG.get('NEEDS_DYNAMIC_CONFIG_ON_COLLECTION', True)): 579 cmd.append('--dynamic-config-url=') 580 if abi_to_run: 581 cmd += ['--abi', abi_to_run] 582 return cmd 583 584 585def _get_special_command_line(modules, _is_public): 586 """This function allows us to split a module like Deqp into segments.""" 587 cmd = [] 588 for module in sorted(modules): 589 cmd += CONFIG['EXTRA_COMMANDLINE'].get(module, []) 590 return cmd 591 592 593def _format_modules_cmd(is_public, 594 abi_to_run, 595 modules=None, 596 retry=False, 597 whole_module_set=None): 598 """Returns list of command tokens for tradefed.""" 599 if retry: 600 assert(CONFIG['TRADEFED_RETRY_COMMAND'] == 'cts' or 601 CONFIG['TRADEFED_RETRY_COMMAND'] == 'retry') 602 603 cmd = ['run', 'commandAndExit', CONFIG['TRADEFED_RETRY_COMMAND'], 604 '--retry', '{session_id}'] 605 else: 606 # For runs create a logcat file for each individual failure. 607 cmd = ['run', 'commandAndExit', CONFIG['TRADEFED_CTS_COMMAND']] 608 609 special_cmd = _get_special_command_line(modules, is_public) 610 if special_cmd: 611 cmd.extend(special_cmd) 612 elif _ALL in modules: 613 pass 614 elif len(modules) == 1: 615 cmd += ['--module', list(modules)[0]] 616 else: 617 if whole_module_set is None: 618 assert (CONFIG['TRADEFED_CTS_COMMAND'] != 'cts-instant'), \ 619 'cts-instant cannot include multiple modules' 620 # We run each module with its own --include-filter option. 621 # https://source.android.com/compatibility/cts/run 622 for module in sorted(modules): 623 cmd += ['--include-filter', module] 624 else: 625 # CTS-Instant does not support --include-filter due to 626 # its implementation detail. Instead, exclude the complement. 627 for module in whole_module_set - set(modules): 628 cmd += ['--exclude-filter', module] 629 630 # For runs create a logcat file for each individual failure. 631 # Not needed on moblab, nobody is going to look at them. 632 if (not modules.intersection(CONFIG['DISABLE_LOGCAT_ON_FAILURE']) and 633 not is_public and 634 CONFIG['TRADEFED_CTS_COMMAND'] != 'gts'): 635 cmd.append('--logcat-on-failure') 636 637 if CONFIG['TRADEFED_IGNORE_BUSINESS_LOGIC_FAILURE']: 638 cmd.append('--ignore-business-logic-failure') 639 640 if CONFIG['TRADEFED_DISABLE_REBOOT']: 641 cmd.append('--disable-reboot') 642 if (CONFIG['TRADEFED_MAY_SKIP_DEVICE_INFO'] and 643 not (modules.intersection(CONFIG['BVT_ARC'] + CONFIG['SMOKE'] + 644 CONFIG['NEEDS_DEVICE_INFO']))): 645 cmd.append('--skip-device-info') 646 if abi_to_run: 647 cmd += ['--abi', abi_to_run] 648 # If NEEDS_DYNAMIC_CONFIG is set, disable the feature except on the modules 649 # that explicitly set as needed. 650 if (not is_public and CONFIG.get('NEEDS_DYNAMIC_CONFIG') and 651 not modules.intersection(CONFIG['NEEDS_DYNAMIC_CONFIG'])): 652 cmd.append('--dynamic-config-url=') 653 654 return cmd 655 656 657def get_run_template(modules, 658 is_public, 659 retry=False, 660 abi_to_run=None, 661 whole_module_set=None): 662 """Command to run the modules specified by a control file.""" 663 no_intersection = not modules.intersection(get_collect_modules(is_public)) 664 collect_present = (_COLLECT in modules or _PUBLIC_COLLECT in modules) 665 all_present = _ALL in modules 666 if no_intersection or (all_present and not collect_present): 667 return _format_modules_cmd(is_public, 668 abi_to_run, 669 modules, 670 retry=retry, 671 whole_module_set=whole_module_set) 672 elif collect_present: 673 return _format_collect_cmd(is_public, abi_to_run, retry=retry) 674 return None 675 676def get_retry_template(modules, is_public): 677 """Command to retry the failed modules as specified by a control file.""" 678 return get_run_template(modules, is_public, retry=True) 679 680 681def get_extra_modules_dict(is_public, abi): 682 if not is_public: 683 return CONFIG['EXTRA_MODULES'] 684 685 extra_modules = copy.deepcopy(CONFIG['PUBLIC_EXTRA_MODULES']) 686 if abi in CONFIG['EXTRA_SUBMODULE_OVERRIDE']: 687 for _, submodules in extra_modules.items(): 688 for old, news in CONFIG['EXTRA_SUBMODULE_OVERRIDE'][abi].items(): 689 submodules.remove(old) 690 submodules.extend(news) 691 return { 692 module: { 693 'SUBMODULES': submodules, 694 'SUITES': [CONFIG['MOBLAB_SUITE_NAME']], 695 } for module, submodules in extra_modules.items() 696 } 697 698 699def get_extra_artifacts(modules): 700 artifacts = [] 701 for module in modules: 702 if module in CONFIG['EXTRA_ARTIFACTS']: 703 artifacts += CONFIG['EXTRA_ARTIFACTS'][module] 704 return artifacts 705 706 707def get_extra_artifacts_host(modules): 708 if not 'EXTRA_ARTIFACTS_HOST' in CONFIG: 709 return 710 711 artifacts = [] 712 for module in modules: 713 if module in CONFIG['EXTRA_ARTIFACTS_HOST']: 714 artifacts += CONFIG['EXTRA_ARTIFACTS_HOST'][module] 715 return artifacts 716 717 718def calculate_timeout(modules, suites): 719 """Calculation for timeout of tradefed run. 720 721 Timeout is at least one hour, except if part of BVT_ARC. 722 Notice these do get adjusted dynamically by number of ABIs on the DUT. 723 """ 724 if 'suite:bvt-arc' in suites: 725 return int(3600 * CONFIG['BVT_TIMEOUT']) 726 if CONFIG.get('QUAL_SUITE_NAMES') and \ 727 CONFIG.get('QUAL_TIMEOUT') and \ 728 ((set(CONFIG['QUAL_SUITE_NAMES']) & set(suites)) and \ 729 not (_COLLECT in modules or _PUBLIC_COLLECT in modules)): 730 return int(3600 * CONFIG['QUAL_TIMEOUT']) 731 732 timeout = 0 733 # First module gets 1h (standard), all other half hour extra (heuristic). 734 default_timeout = int(3600 * CONFIG['CTS_TIMEOUT_DEFAULT']) 735 delta = default_timeout 736 for module in modules: 737 if module in CONFIG['CTS_TIMEOUT']: 738 # Modules that run very long are encoded here. 739 timeout += int(3600 * CONFIG['CTS_TIMEOUT'][module]) 740 elif module.startswith('CtsDeqpTestCases.dEQP-VK.'): 741 # TODO: Optimize this temporary hack by reducing this value or 742 # setting appropriate values for each test if possible. 743 timeout = max(timeout, int(3600 * 12)) 744 elif 'Jvmti' in module: 745 # We have too many of these modules and they run fast. 746 timeout += 300 747 else: 748 timeout += delta 749 delta = default_timeout // 2 750 return timeout 751 752 753def needs_push_media(modules): 754 """Oracle to determine if to push several GB of media files to DUT.""" 755 if modules.intersection(set(CONFIG['NEEDS_PUSH_MEDIA'])): 756 return True 757 return False 758 759 760def enable_default_apps(modules): 761 """Oracle to determine if to enable default apps (eg. Files.app).""" 762 if modules.intersection(set(CONFIG['ENABLE_DEFAULT_APPS'])): 763 return True 764 return False 765 766 767def get_controlfile_content(combined, 768 modules, 769 abi, 770 revision, 771 build, 772 uri, 773 suites=None, 774 is_public=False, 775 is_latest=False, 776 led_provision=None, 777 camera_facing=None, 778 whole_module_set=None): 779 """Returns the text inside of a control file. 780 781 @param combined: name to use for this combination of modules. 782 @param modules: set of CTS modules which will be tested in the control 783 file. If 'all' is specified, the control file will run 784 all the tests. 785 """ 786 # We tag results with full revision now to get result directories containing 787 # the revision. This fits stainless/ better. 788 tag = '%s' % get_extension(combined, abi, revision, is_public, led_provision, 789 camera_facing) 790 # For test_that the NAME should be the same as for the control file name. 791 # We could try some trickery here to get shorter extensions for a default 792 # suite/ARM. But with the monthly uprevs this will quickly get confusing. 793 name = '%s.%s' % (CONFIG['TEST_NAME'], tag) 794 if not suites: 795 suites = get_suites(modules, abi, is_public, camera_facing) 796 attributes = ', '.join(suites) 797 uri = 'LATEST' if is_latest else (None if is_public else uri) 798 target_module = None 799 if (combined not in get_collect_modules(is_public) and combined != _ALL): 800 target_module = combined 801 for target, config in get_extra_modules_dict(is_public, abi).items(): 802 if combined in config['SUBMODULES']: 803 target_module = target 804 return _CONTROLFILE_TEMPLATE.render( 805 year=CONFIG['COPYRIGHT_YEAR'], 806 name=name, 807 base_name=CONFIG['TEST_NAME'], 808 test_func_name=CONFIG['CONTROLFILE_TEST_FUNCTION_NAME'], 809 attributes=attributes, 810 dependencies=get_dependencies(modules, abi, is_public, 811 led_provision, camera_facing), 812 extra_artifacts=get_extra_artifacts(modules), 813 extra_artifacts_host=get_extra_artifacts_host(modules), 814 job_retries=get_job_retries(modules, is_public, suites), 815 max_result_size_kb=get_max_result_size_kb(modules, is_public), 816 revision=revision, 817 build=build, 818 abi=abi, 819 needs_push_media=needs_push_media(modules), 820 enable_default_apps=enable_default_apps(modules), 821 tag=tag, 822 uri=uri, 823 DOC=get_doc(modules, abi, is_public), 824 servo_support_needed=servo_support_needed(modules, is_public), 825 max_retries=get_max_retries(modules, abi, suites, is_public), 826 timeout=calculate_timeout(modules, suites), 827 run_template=get_run_template(modules, 828 is_public, 829 abi_to_run=CONFIG.get( 830 'REPRESENTATIVE_ABI', 831 {}).get(abi, None), 832 whole_module_set=whole_module_set), 833 retry_template=get_retry_template(modules, is_public), 834 target_module=target_module, 835 target_plan=None, 836 test_length=get_test_length(modules), 837 priority=get_test_priority(modules, is_public), 838 extra_args=get_extra_args(modules, is_public), 839 authkey=get_authkey(is_public), 840 sync_count=get_sync_count(modules, abi, is_public), 841 camera_facing=camera_facing) 842 843 844def get_tradefed_data(path, is_public, abi): 845 """Queries tradefed to provide us with a list of modules. 846 847 Notice that the parsing gets broken at times with major new CTS drops. 848 """ 849 tradefed = os.path.join(path, CONFIG['TRADEFED_EXECUTABLE_PATH']) 850 # Forgive me for I have sinned. Same as: chmod +x tradefed. 851 os.chmod(tradefed, os.stat(tradefed).st_mode | stat.S_IEXEC) 852 cmd_list = [tradefed, 'list', 'modules'] 853 logging.info('Calling tradefed for list of modules.') 854 with open(os.devnull, 'w') as devnull: 855 # tradefed terminates itself if stdin is not a tty. 856 tradefed_output = subprocess.check_output(cmd_list, stdin=devnull) 857 858 # TODO(ihf): Get a tradefed command which terminates then refactor. 859 p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE) 860 modules = set() 861 build = '<unknown>' 862 line = '' 863 revision = None 864 is_in_intaractive_mode = True 865 # The process does not terminate, but we know the last test is vm-tests-tf. 866 while True: 867 line = p.stdout.readline().strip() 868 # Android Compatibility Test Suite 7.0 (3423912) 869 if (line.startswith('Android Compatibility Test Suite ') 870 or line.startswith('Android Google ') 871 or line.startswith('Android Vendor Test Suite') 872 or line.startswith('Android Security Test Suite')): 873 logging.info('Unpacking: %s.', line) 874 build = get_tradefed_build(line) 875 revision = get_tradefed_revision(line) 876 elif line.startswith('Non-interactive mode: '): 877 is_in_intaractive_mode = False 878 elif line.startswith('arm') or line.startswith('x86'): 879 # Newer CTS shows ABI-module pairs like "arm64-v8a CtsNetTestCases" 880 line = line.split()[1] 881 if line not in CONFIG.get('EXCLUDE_MODULES', []): 882 modules.add(line) 883 elif line.startswith('Cts'): 884 modules.add(line) 885 elif line.startswith('Gts'): 886 # Older GTS plainly lists the module names 887 modules.add(line) 888 elif line.startswith('cts-'): 889 modules.add(line) 890 elif line.startswith('signed-Cts'): 891 modules.add(line) 892 elif line.startswith('vm-tests-tf'): 893 modules.add(line) 894 break # TODO(ihf): Fix using this as EOS. 895 elif not line: 896 exit_code = p.poll() 897 if exit_code is not None: 898 # The process has automatically exited. 899 if is_in_intaractive_mode or exit_code != 0: 900 # The process exited unexpectedly in interactive mode, 901 # or exited with error in non-interactive mode. 902 logging.warning( 903 'The process has exited unexpectedly (exit code: %d)', 904 exit_code) 905 modules = set() 906 break 907 elif line.isspace() or line.startswith('Use "help"'): 908 pass 909 else: 910 logging.warning('Ignoring "%s"', line) 911 if p.poll() is None: 912 # Kill the process if alive. 913 p.kill() 914 p.wait() 915 916 if not modules: 917 raise Exception("no modules found.") 918 return list(modules), build, revision 919 920 921def download(uri, destination): 922 """Download |uri| to local |destination|. 923 924 |destination| must be a file path (not a directory path).""" 925 if uri.startswith('http://') or uri.startswith('https://'): 926 subprocess.check_call(['wget', uri, '-O', destination]) 927 elif uri.startswith('gs://'): 928 subprocess.check_call(['gsutil', 'cp', uri, destination]) 929 else: 930 raise Exception 931 932 933@contextlib.contextmanager 934def pushd(d): 935 """Defines pushd.""" 936 current = os.getcwd() 937 os.chdir(d) 938 try: 939 yield 940 finally: 941 os.chdir(current) 942 943 944def unzip(filename, destination): 945 """Unzips a zip file to the destination directory.""" 946 with pushd(destination): 947 # We are trusting Android to have a valid zip file for us. 948 with zipfile.ZipFile(filename) as zf: 949 zf.extractall() 950 951 952def get_collect_modules(is_public): 953 if is_public: 954 return set([_PUBLIC_COLLECT]) 955 return set([_COLLECT]) 956 957 958@contextlib.contextmanager 959def TemporaryDirectory(prefix): 960 """Poor man's python 3.2 import.""" 961 tmp = tempfile.mkdtemp(prefix=prefix) 962 try: 963 yield tmp 964 finally: 965 shutil.rmtree(tmp) 966 967 968def get_word_pattern(m, l=1): 969 """Return the first few words of the CamelCase module name. 970 971 Break after l+1 CamelCase word. 972 Example: CtsDebugTestCases -> CtsDebug. 973 """ 974 s = re.findall('^[a-z-]+|[A-Z]*[^A-Z0-9]*', m)[0:l + 1] 975 # Ignore Test or TestCases at the end as they don't add anything. 976 if len(s) > l: 977 if s[l].startswith('Test') or s[l].startswith('['): 978 return ''.join(s[0:l]) 979 if s[l - 1] == 'Test' and s[l].startswith('Cases'): 980 return ''.join(s[0:l - 1]) 981 return ''.join(s[0:l + 1]) 982 983 984def combine_modules_by_common_word(modules): 985 """Returns a dictionary of (combined name, set of module) pairs. 986 987 This gives a mild compaction of control files (from about 320 to 135). 988 Example: 989 'CtsVoice' -> ['CtsVoiceInteractionTestCases', 'CtsVoiceSettingsTestCases'] 990 """ 991 d = dict() 992 # On first pass group modules with common first word together. 993 for module in modules: 994 pattern = get_word_pattern(module) 995 v = d.get(pattern, []) 996 v.append(module) 997 v.sort() 998 d[pattern] = v 999 # Second pass extend names to maximum common prefix. This keeps control file 1000 # names identical if they contain only one module and less ambiguous if they 1001 # contain multiple modules. 1002 combined = dict() 1003 for key in sorted(d): 1004 # Instead if a one syllable prefix use longest common prefix of modules. 1005 prefix = os.path.commonprefix(d[key]) 1006 # Beautification: strip Tests/TestCases from end of prefix, but only if 1007 # there is more than one module in the control file. This avoids 1008 # slightly strange combination of having CtsDpiTestCases1/2 inside of 1009 # CtsDpiTestCases (now just CtsDpi to make it clearer there are several 1010 # modules in this control file). 1011 if len(d[key]) > 1: 1012 prefix = re.sub('TestCases$', '', prefix) 1013 prefix = re.sub('Tests$', '', prefix) 1014 # Beautification: CtsMedia files run very long and are unstable. Give 1015 # each module its own control file, even though this heuristic would 1016 # lump them together. 1017 if prefix.startswith('CtsMedia'): 1018 # Separate each CtsMedia* modules, but group extra modules with 1019 # optional parametrization (ex: secondary_user, instant) together. 1020 prev = ' ' 1021 for media in sorted(d[key]): 1022 if media.startswith(prev): 1023 combined[prev].add(media) 1024 else: 1025 prev = media 1026 combined[media] = set([media]) 1027 1028 else: 1029 combined[prefix] = set(d[key]) 1030 print('Reduced number of control files from %d to %d.' % (len(modules), 1031 len(combined))) 1032 return combined 1033 1034 1035def combine_modules_by_bookmark(modules): 1036 """Return a manually curated list of name, module pairs. 1037 1038 Ideally we split "all" into a dictionary of maybe 10-20 equal runtime parts. 1039 (Say 2-5 hours each.) But it is ok to run problematic modules alone. 1040 """ 1041 d = dict() 1042 # Figure out sets of modules between bookmarks. Not optimum time complexity. 1043 for bookmark in CONFIG['QUAL_BOOKMARKS']: 1044 if modules: 1045 for module in sorted(modules): 1046 if module < bookmark: 1047 v = d.get(bookmark, set()) 1048 v.add(module) 1049 d[bookmark] = v 1050 # Remove processed modules. 1051 if bookmark in d: 1052 modules = modules - d[bookmark] 1053 # Clean up names. 1054 combined = dict() 1055 for key in sorted(d): 1056 v = sorted(d[key]) 1057 # New name is first element '_-_' last element. 1058 # Notice there is a bug in $ADB_VENDOR_KEYS path name preventing 1059 # arbitrary characters. 1060 prefix = v[0] + '_-_' + v[-1] 1061 combined[prefix] = set(v) 1062 return combined 1063 1064 1065def write_controlfile(name, 1066 modules, 1067 abi, 1068 revision, 1069 build, 1070 uri, 1071 suites, 1072 is_public, 1073 is_latest=False, 1074 whole_module_set=None): 1075 """Write a single control file.""" 1076 filename = get_controlfile_name(name, abi, revision, is_public) 1077 content = get_controlfile_content(name, 1078 modules, 1079 abi, 1080 revision, 1081 build, 1082 uri, 1083 suites, 1084 is_public, 1085 is_latest, 1086 whole_module_set=whole_module_set) 1087 with open(filename, 'w') as f: 1088 f.write(content) 1089 1090 1091def write_moblab_controlfiles(modules, abi, revision, build, uri, is_public): 1092 """Write all control files for moblab. 1093 1094 Nothing gets combined. 1095 1096 Moblab uses one module per job. In some cases like Deqp which can run super 1097 long it even creates several jobs per module. Moblab can do this as it has 1098 less relative overhead spinning up jobs than the lab. 1099 """ 1100 for module in modules: 1101 # No need to generate control files with extra suffix, since --module 1102 # option will cover variants with optional parameters. 1103 if "[" in module: 1104 continue 1105 write_controlfile(module, set([module]), abi, revision, build, uri, 1106 [CONFIG['MOBLAB_SUITE_NAME']], is_public) 1107 1108 1109def write_regression_controlfiles(modules, abi, revision, build, uri, 1110 is_public, is_latest): 1111 """Write all control files for stainless/ToT regression lab coverage. 1112 1113 Regression coverage on tot currently relies heavily on watching stainless 1114 dashboard and sponge. So instead of running everything in a single run 1115 we split CTS into many jobs. It used to be one job per module, but that 1116 became too much in P (more than 300 per ABI). Instead we combine modules 1117 with similar names and run these in the same job (alphabetically). 1118 """ 1119 combined = combine_modules_by_common_word(set(modules)) 1120 for key in combined: 1121 write_controlfile(key, combined[key], abi, revision, build, uri, None, 1122 is_public, is_latest) 1123 1124 1125def write_qualification_controlfiles(modules, abi, revision, build, uri, 1126 is_public): 1127 """Write all control files to run "all" tests for qualification. 1128 1129 Qualification was performed on N by running all tests using tradefed 1130 sharding (specifying SYNC_COUNT=2) in the control files. In skylab 1131 this is currently not implemented, so we fall back to autotest sharding 1132 all CTS tests into 10-20 hand chosen shards. 1133 """ 1134 combined = combine_modules_by_bookmark(set(modules)) 1135 for key in combined: 1136 write_controlfile('all.' + key, combined[key], abi, revision, build, 1137 uri, CONFIG.get('QUAL_SUITE_NAMES'), is_public) 1138 1139 1140def write_qualification_and_regression_controlfile(modules, abi, revision, 1141 build, uri, is_public): 1142 """Write a control file to run "all" tests for qualification and regression. 1143 """ 1144 # For cts-instant, qualication control files are expected to cover 1145 # regressions as well. Hence the 'suite:arc-cts' is added. 1146 suites = ['suite:arc-cts', 'suite:arc-cts-qual'] 1147 module_set = set(modules) 1148 combined = combine_modules_by_bookmark(module_set) 1149 for key in combined: 1150 write_controlfile('all.' + key, 1151 combined[key], 1152 abi, 1153 revision, 1154 build, 1155 uri, 1156 suites, 1157 is_public, 1158 whole_module_set=module_set) 1159 1160 1161def write_collect_controlfiles(_modules, abi, revision, build, uri, is_public, 1162 is_latest): 1163 """Write all control files for test collection used as reference to 1164 1165 compute completeness (missing tests) on the CTS dashboard. 1166 """ 1167 if is_public: 1168 suites = [CONFIG['MOBLAB_SUITE_NAME']] 1169 else: 1170 suites = CONFIG['INTERNAL_SUITE_NAMES'] \ 1171 + CONFIG.get('QUAL_SUITE_NAMES', []) 1172 for module in get_collect_modules(is_public): 1173 write_controlfile(module, set([module]), abi, revision, build, uri, 1174 suites, is_public, is_latest) 1175 1176 1177def write_extra_controlfiles(_modules, abi, revision, build, uri, is_public, 1178 is_latest): 1179 """Write all extra control files as specified in config. 1180 1181 This is used by moblab to load balance large modules like Deqp, as well as 1182 making custom modules such as WM presubmit. A similar approach was also used 1183 during bringup of grunt to split media tests. 1184 """ 1185 for module, config in get_extra_modules_dict(is_public, abi).items(): 1186 for submodule in config['SUBMODULES']: 1187 write_controlfile(submodule, set([submodule]), abi, revision, 1188 build, uri, config['SUITES'], is_public, 1189 is_latest) 1190 1191 1192def write_extra_camera_controlfiles(abi, revision, build, uri, is_public): 1193 """Control files for CtsCameraTestCases.camerabox.*""" 1194 module = 'CtsCameraTestCases' 1195 for facing in ['back', 'front']: 1196 for led_provision in ['led', 'noled']: 1197 name = get_controlfile_name(module, abi, 1198 revision, is_public, led_provision, facing) 1199 content = get_controlfile_content(module, 1200 set([module]), 1201 abi, 1202 revision, 1203 build, 1204 uri, 1205 None, 1206 is_public, 1207 led_provision=led_provision, 1208 camera_facing=facing) 1209 with open(name, 'w') as f: 1210 f.write(content) 1211 1212 1213def run(uris, is_public, is_latest, cache_dir): 1214 """Downloads each bundle in |uris| and generates control files for each 1215 1216 module as reported to us by tradefed. 1217 """ 1218 for uri in uris: 1219 abi = get_bundle_abi(uri) 1220 # Get tradefed data by downloading & unzipping the files 1221 with TemporaryDirectory(prefix='cts-android_') as tmp: 1222 if cache_dir is not None: 1223 assert(os.path.isdir(cache_dir)) 1224 bundle = os.path.join(cache_dir, os.path.basename(uri)) 1225 if not os.path.exists(bundle): 1226 logging.info('Downloading to %s.', cache_dir) 1227 download(uri, bundle) 1228 else: 1229 bundle = os.path.join(tmp, os.path.basename(uri)) 1230 logging.info('Downloading to %s.', tmp) 1231 download(uri, bundle) 1232 logging.info('Extracting %s.', bundle) 1233 unzip(bundle, tmp) 1234 modules, build, revision = get_tradefed_data(tmp, is_public, abi) 1235 if not revision: 1236 raise Exception('Could not determine revision.') 1237 1238 logging.info('Writing all control files.') 1239 if is_public: 1240 write_moblab_controlfiles(modules, abi, revision, build, uri, 1241 is_public) 1242 else: 1243 if CONFIG['CONTROLFILE_WRITE_SIMPLE_QUAL_AND_REGRESS']: 1244 write_qualification_and_regression_controlfile( 1245 modules, abi, revision, build, uri, is_public) 1246 else: 1247 write_regression_controlfiles(modules, abi, revision, 1248 build, uri, is_public, 1249 is_latest) 1250 write_qualification_controlfiles(modules, abi, revision, 1251 build, uri, is_public) 1252 1253 if CONFIG['CONTROLFILE_WRITE_CAMERA']: 1254 write_extra_camera_controlfiles(abi, revision, build, uri, 1255 is_public) 1256 1257 if CONFIG.get('CONTROLFILE_WRITE_COLLECT', True): 1258 write_collect_controlfiles(modules, abi, revision, build, uri, 1259 is_public, is_latest) 1260 1261 if CONFIG['CONTROLFILE_WRITE_EXTRA']: 1262 write_extra_controlfiles(None, abi, revision, build, uri, 1263 is_public, is_latest) 1264 1265 1266def main(config): 1267 """ Entry method of generator """ 1268 1269 global CONFIG 1270 CONFIG = config 1271 1272 logging.basicConfig(level=logging.INFO) 1273 parser = argparse.ArgumentParser( 1274 description='Create control files for a CTS bundle on GS.', 1275 formatter_class=argparse.RawTextHelpFormatter) 1276 parser.add_argument( 1277 'uris', 1278 nargs='+', 1279 help='List of Google Storage URIs to CTS bundles. Example:\n' 1280 'gs://chromeos-arc-images/cts/bundle/P/' 1281 'android-cts-9.0_r9-linux_x86-x86.zip') 1282 parser.add_argument( 1283 '--is_public', 1284 dest='is_public', 1285 default=False, 1286 action='store_true', 1287 help='Generate the public control files for CTS, default generate' 1288 ' the internal control files') 1289 parser.add_argument( 1290 '--is_latest', 1291 dest='is_latest', 1292 default=False, 1293 action='store_true', 1294 help='Generate the control files for CTS from the latest CTS bundle' 1295 ' stored in the internal storage') 1296 parser.add_argument( 1297 '--cache_dir', 1298 dest='cache_dir', 1299 default=None, 1300 action='store', 1301 help='Cache directory for downloaded bundle file. Uses the cached ' 1302 'bundle file if exists, or caches a downloaded file to this ' 1303 'directory if not.') 1304 args = parser.parse_args() 1305 run(args.uris, args.is_public, args.is_latest, args.cache_dir) 1306