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