• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# Copyright 2016 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 logging
9import os
10import re
11import shutil
12import stat
13import subprocess
14import tempfile
15import zipfile
16
17_CONTROLFILE_TEMPLATE = """\
18# Copyright 2016 The Chromium OS Authors. All rights reserved.
19# Use of this source code is governed by a BSD-style license that can be
20# found in the LICENSE file.
21
22# This file has been automatically generated. Do not edit!
23
24AUTHOR = 'ARC Team'
25NAME = '{name}'
26ATTRIBUTES = '{attributes}'
27DEPENDENCIES = '{dependencies}'
28JOB_RETRIES = {retries}
29TEST_TYPE = 'server'
30TIME = 'LENGTHY'
31
32DOC = ('Run package {package} of the '
33       'Android {revision} Compatibility Test Suite (CTS), build {build},'
34       'using {abi} ABI in the ARC container.')
35
36def run_CTS(machine):
37    host = hosts.create_host(machine)
38    job.run_test(
39        'cheets_CTS',
40        host=host,
41        iterations=1,{retry}
42        tag='{tag}',
43        target_package='{package}',
44        {source},
45        timeout={timeout})
46
47parallel_simple(run_CTS, machines)"""
48
49_CTS_TIMEOUT = {
50    'android.core.tests.libcore.package.org': 90,
51    'android.core.vm-tests-tf': 90,
52    'android.host.security': 90,
53    'android.media': 360,
54    'android.mediastress': 480,
55    'android.print': 180,
56    'com.android.cts.filesystemperf': 180,
57    'com.drawelements.deqp.gles3': 240,
58    'com.drawelements.deqp.gles31': 90,
59    'com.drawelements.deqp.gles31.copy_image_mixed': 120,
60    'com.drawelements.deqp.gles31.copy_image_non_compressed': 240,
61}
62
63_SUITE_SMOKE = [
64    'android.core.tests.libcore.package.harmony_java_math'
65]
66
67# Any test in SMOKE (VMTest) should also be in CQ (HWTest).
68_SUITE_BVT_CQ = _SUITE_SMOKE + [
69    'com.android.cts.dram'
70]
71
72_SUITE_ARC_BVT_CQ = _SUITE_BVT_CQ
73
74_SUITE_BVT_PERBUILD = [
75    'android.signature',
76    'android.speech',
77    'android.systemui',
78    'android.telecom',
79    'android.telephony',
80    'android.theme',
81    'android.transition',
82    'android.tv',
83    'android.uiautomation',
84    'android.usb',
85    'android.voicesettings',
86    'com.android.cts.filesystemperf',
87    'com.android.cts.jank',
88    'com.android.cts.opengl',
89    'com.android.cts.simplecpu',
90]
91
92def get_tradefed_build(line):
93    """Gets the build of Android CTS from tradefed.
94
95    @param line Tradefed identification output on startup. Example:
96                Android CTS 6.0_r6 build:2813453
97    @return Tradefed CTS build. Example: 2813453.
98    """
99    # Sample string: Android CTS 6.0_r6 build:2813453.
100    m = re.search(r'(?<=build:)(.*)', line)
101    if m:
102        return m.group(0)
103    logging.warning('Could not identify build in line "%s".', line)
104    return '<unknown>'
105
106
107def get_tradefed_revision(line):
108    """Gets the revision of Android CTS from tradefed.
109
110    @param line Tradefed identification output on startup. Example:
111                Android CTS 6.0_r6 build:2813453
112    @return Tradefed CTS revision. Example: 6.0_r6.
113    """
114    m = re.search(r'(?<=Android CTS )(.*) build:', line)
115    if m:
116        return m.group(1)
117    logging.warning('Could not identify revision in line "%s".', line)
118    return None
119
120
121def get_bundle_abi(filename):
122    """Makes an educated guess about the ABI.
123
124    In this case we chose to guess by filename, but we could also parse the
125    xml files in the package. (Maybe this needs to be done in the future.)
126    """
127    if filename.endswith('_x86-arm.zip'):
128        return 'arm'
129    if filename.endswith('_x86-x86.zip'):
130        return 'x86'
131    raise Exception('Could not determine ABI from "%s".' % filename)
132
133
134def get_bundle_revision(filename):
135    """Makes an educated guess about the revision.
136
137    In this case we chose to guess by filename, but we could also parse the
138    xml files in the package.
139    """
140    m = re.search(r'(?<=android-cts-)(.*)-linux', filename)
141    if m is not None:
142        return m.group(1)
143    return None
144
145
146def get_extension(package, abi, revision):
147    """Defines a unique string.
148
149    Notice we chose package revision first, then abi, as the package revision
150    changes at least on a monthly basis. This ordering makes it simpler to
151    add/remove packages.
152    """
153    return '%s.%s.%s' % (revision, abi, package)
154
155
156def get_controlfile_name(package, abi, revision):
157    """Defines the control file name."""
158    return 'control.%s' % get_extension(package, abi, revision)
159
160
161def get_public_extension(package, abi):
162    return '%s.%s' % (abi, package)
163
164
165def get_public_controlfile_name(package, abi):
166    """Defines the public control file name."""
167    return 'control.%s' % get_public_extension(package, abi)
168
169
170def get_attribute_suites(package, abi):
171    """Defines the suites associated with a package."""
172    attributes = 'suite:arc-cts'
173    # Get a minmum amount of coverage on cq (one quick CTS package only).
174    if package in _SUITE_SMOKE:
175        attributes += ', suite:smoke'
176    if package in _SUITE_BVT_CQ:
177        attributes += ', suite:bvt-cq'
178    if package in _SUITE_ARC_BVT_CQ:
179        attributes += ', suite:arc-bvt-cq'
180    if package in _SUITE_BVT_PERBUILD and abi == 'arm':
181        attributes += ', suite:bvt-perbuild'
182    # Adding arc-cts-stable runs all packages twice on stable.
183    return attributes
184
185
186def get_dependencies(abi):
187    """Defines lab dependencies needed to schedule a package.
188
189    Currently we only care about x86 ABI tests, which must run on Intel boards.
190    """
191    # TODO(ihf): Enable this once crbug.com/618509 has labeled all lab DUTs.
192    if abi == 'x86':
193        return 'arc, cts_abi_x86'
194    return 'arc'
195
196# suite_scheduler retries
197_RETRIES = 2
198
199
200def get_controlfile_content(package, abi, revision, build, uri):
201    """Returns the text inside of a control file."""
202    # For test_that the NAME should be the same as for the control file name.
203    # We could try some trickery here to get shorter extensions for a default
204    # suite/ARM. But with the monthly uprevs this will quickly get confusing.
205    name = 'cheets_CTS.%s' % get_extension(package, abi, revision)
206    if is_public(uri):
207        name = 'cheets_CTS.%s' % get_public_extension(package, abi)
208    dependencies = get_dependencies(abi)
209    retries = _RETRIES
210    # We put all revisions and all abi in the same tag for wmatrix, otherwise
211    # the list at https://wmatrix.googleplex.com/tests would grow way too large.
212    # Example: for all values of |REVISION| and |ABI| we map control file
213    # cheets_CTS.REVISION.ABI.PACKAGE on wmatrix to cheets_CTS.PACKAGE.
214    # This way people can find results independent of |REVISION| and |ABI| at
215    # the same URL.
216    tag = package
217    timeout = 3600
218    if package in _CTS_TIMEOUT:
219        timeout = 60 * _CTS_TIMEOUT[package]
220    if is_public(uri):
221        source = "bundle='%s'" % abi
222        attributes = 'suite:cts'
223    else:
224        source = "uri='%s'" % uri
225        attributes = '%s, test_name:%s' % (get_attribute_suites(package, abi),
226                                           name)
227    # cheets_CTS internal retries limited due to time constraints on cq.
228    retry = ''
229    if (package in (_SUITE_SMOKE + _SUITE_BVT_CQ + _SUITE_ARC_BVT_CQ) or
230        package in _SUITE_BVT_PERBUILD and abi == 'arm'):
231        retry = '\n        max_retry=3,'
232    return _CONTROLFILE_TEMPLATE.format(
233        name=name,
234        attributes=attributes,
235        dependencies=dependencies,
236        retries=retries,
237        package=package,
238        revision=revision,
239        build=build,
240        abi=abi,
241        tag=tag,
242        source=source,
243        retry=retry,
244        timeout=timeout)
245
246
247def get_tradefed_data(path):
248    """Queries tradefed to provide us with a list of packages."""
249    tradefed = os.path.join(path, 'android-cts/tools/cts-tradefed')
250    # Forgive me for I have sinned. Same as: chmod +x tradefed.
251    os.chmod(tradefed, os.stat(tradefed).st_mode | stat.S_IEXEC)
252    cmd_list = [tradefed, 'list', 'packages']
253    logging.info('Calling tradefed for list of packages.')
254    # TODO(ihf): Get a tradefed command which terminates then refactor.
255    p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE)
256    packages = []
257    build = '<unknown>'
258    line = ''
259    revision = None
260    # The process does not terminate, but we know the last test starts with zzz.
261    while not line.startswith('zzz'):
262        line = p.stdout.readline().strip()
263        if line:
264            print line
265        if line.startswith('Android CTS '):
266            logging.info('Unpacking: %s.', line)
267            build = get_tradefed_build(line)
268            revision = get_tradefed_revision(line)
269        elif re.search(r'^(android\.|com\.|zzz\.)', line):
270            packages.append(line)
271    p.kill()
272    p.wait()
273    return packages, build, revision
274
275
276def is_public(uri):
277    if uri.startswith('gs:'):
278        return False
279    elif uri.startswith('https://dl.google.com'):
280        return True
281    raise
282
283
284def download(uri, destination):
285    """Download |uri| to local |destination|."""
286    if is_public(uri):
287        subprocess.check_call(['wget', uri, '-P', destination])
288    else:
289        subprocess.check_call(['gsutil', 'cp', uri, destination])
290
291
292@contextlib.contextmanager
293def pushd(d):
294    """Defines pushd."""
295    current = os.getcwd()
296    os.chdir(d)
297    try:
298        yield
299    finally:
300        os.chdir(current)
301
302
303def unzip(filename, destination):
304    """Unzips a zip file to the destination directory."""
305    with pushd(destination):
306        # We are trusting Android to have a sane zip file for us.
307        with zipfile.ZipFile(filename) as zf:
308            zf.extractall()
309
310
311@contextlib.contextmanager
312def TemporaryDirectory(prefix):
313    """Poor man's python 3.2 import."""
314    tmp = tempfile.mkdtemp(prefix=prefix)
315    try:
316        yield tmp
317    finally:
318        shutil.rmtree(tmp)
319
320
321def main(uris):
322    """Downloads each package in |uris| and generates control files."""
323    for uri in uris:
324        abi = get_bundle_abi(uri)
325        with TemporaryDirectory(prefix='cts-android_') as tmp:
326            logging.info('Downloading to %s.', tmp)
327            bundle = os.path.join(tmp, 'cts-android.zip')
328            download(uri, tmp)
329            bundle = os.path.join(tmp, os.path.basename(uri))
330            logging.info('Extracting %s.', bundle)
331            unzip(bundle, tmp)
332            packages, build, revision = get_tradefed_data(tmp)
333            if not revision:
334                raise Exception('Could not determine revision.')
335            # And write all control files.
336            logging.info('Writing all control files.')
337            for package in packages:
338                name = get_controlfile_name(package, abi, revision)
339                if is_public(uri):
340                    name = get_public_controlfile_name(package, abi)
341                content = get_controlfile_content(package, abi, revision, build,
342                                                  uri)
343                with open(name, 'w') as f:
344                    f.write(content)
345
346
347if __name__ == '__main__':
348    logging.basicConfig(level=logging.INFO)
349    parser = argparse.ArgumentParser(
350        description='Create control files for a CTS bundle on GS.',
351        formatter_class=argparse.RawTextHelpFormatter)
352    parser.add_argument(
353        'uris',
354        nargs='+',
355        help='List of Google Storage URIs to CTS bundles. Example:\n'
356        'gs://chromeos-arc-images/cts/bundle/2016-06-02/'
357        'android-cts-6.0_r6-linux_x86-arm.zip')
358    args = parser.parse_args()
359    main(args.uris)
360