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