• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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