1#!/usr/bin/env python 2# Copyright 2015 The Chromium 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'''Unittests for update.py. 7 8They set up a temporary directory that is used to mock a bucket, the directory 9containing the configuration files and the android sdk directory. 10 11Tests run the script with various inputs and check the status of the filesystem 12''' 13 14import shutil 15import tempfile 16import unittest 17import os 18import sys 19import zipfile 20import contextlib 21 22sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) 23from play_services import update 24 25 26class TestFunctions(unittest.TestCase): 27 DEFAULT_CONFIG_VERSION = 42 28 DEFAULT_LICENSE = 'Default License' 29 DEFAULT_ZIP_SHA1 = 'zip0and0filling0to0forty0chars0000000000' 30 31 def __init__(self, *args, **kwargs): 32 super(TestFunctions, self).__init__(*args, **kwargs) 33 self.paths = None # Initialized in SetUpWorkdir 34 self.workdir = None # Initialized in setUp 35 36 #override 37 def setUp(self): 38 self.workdir = tempfile.mkdtemp() 39 40 #override 41 def tearDown(self): 42 shutil.rmtree(self.workdir) 43 self.workdir = None 44 45 def testUpload(self): 46 version = 1337 47 self.SetUpWorkdir( 48 xml_version=version, 49 gms_lib=True, 50 source_prop=True) 51 52 status = update.main([ 53 'upload', 54 '--dry-run', 55 '--skip-git', 56 '--bucket', self.paths.bucket, 57 '--config', self.paths.config_file, 58 '--sdk-root', self.paths.sdk_root 59 ]) 60 self.assertEqual(status, 0, 'the command should have succeeded.') 61 62 # bucket should contain license, name = license.sha1 63 self.assertTrue(os.path.isfile(self.paths.config_license_sha1)) 64 license_sha1 = _GetFileContent(self.paths.config_license_sha1) 65 bucket_license = os.path.join(self.paths.bucket, str(version), 66 license_sha1) 67 self.assertTrue(os.path.isfile(bucket_license)) 68 self.assertEqual(_GetFileContent(bucket_license), self.DEFAULT_LICENSE) 69 70 # bucket should contain zip, name = zip.sha1 71 self.assertTrue(os.path.isfile(self.paths.config_zip_sha1)) 72 bucket_zip = os.path.join(self.paths.bucket, str(version), 73 _GetFileContent(self.paths.config_zip_sha1)) 74 self.assertTrue(os.path.isfile(bucket_zip)) 75 76 # unzip, should contain expected files 77 with zipfile.ZipFile(bucket_zip, "r") as bucket_zip_file: 78 self.assertEqual(bucket_zip_file.namelist(), 79 ['dummy_file', 'res/values/version.xml']) 80 81 def testUploadAlreadyLatestVersion(self): 82 self.SetUpWorkdir( 83 xml_version=self.DEFAULT_CONFIG_VERSION, 84 gms_lib=True, 85 source_prop=True) 86 87 status = update.main([ 88 'upload', 89 '--dry-run', 90 '--skip-git', 91 '--bucket', self.paths.bucket, 92 '--config', self.paths.config_file, 93 '--sdk-root', self.paths.sdk_root, 94 ]) 95 self.assertEqual(status, 0, 'the command should have succeeded.') 96 97 # bucket should be empty 98 self.assertFalse(os.listdir(self.paths.bucket)) 99 self.assertFalse(os.path.isfile(self.paths.config_license_sha1)) 100 self.assertFalse(os.path.isfile(self.paths.config_zip_sha1)) 101 102 def testDownload(self): 103 self.SetUpWorkdir(populate_bucket=True) 104 105 with _MockedInput('y'): 106 status = update.main([ 107 'download', 108 '--dry-run', 109 '--bucket', self.paths.bucket, 110 '--config', self.paths.config_file, 111 '--sdk-root', self.paths.sdk_root, 112 ]) 113 114 self.assertEqual(status, 0, 'the command should have succeeded.') 115 116 # sdk_root should contain zip contents, zip sha1, license 117 self.assertTrue(os.path.isfile(os.path.join(self.paths.gms_lib, 118 'dummy_file'))) 119 self.assertTrue(os.path.isfile(self.paths.gms_root_sha1)) 120 self.assertTrue(os.path.isfile(self.paths.gms_root_license)) 121 self.assertEquals(_GetFileContent(self.paths.gms_root_license), 122 self.DEFAULT_LICENSE) 123 124 def testDownloadBot(self): 125 self.SetUpWorkdir(populate_bucket=True, bot_env=True) 126 127 # No need to type 'y' on bots 128 status = update.main([ 129 'download', 130 '--dry-run', 131 '--bucket', self.paths.bucket, 132 '--config', self.paths.config_file, 133 '--sdk-root', self.paths.sdk_root, 134 ]) 135 136 self.assertEqual(status, 0, 'the command should have succeeded.') 137 138 # sdk_root should contain zip contents, zip sha1, license 139 self.assertTrue(os.path.isfile(os.path.join(self.paths.gms_lib, 140 'dummy_file'))) 141 self.assertTrue(os.path.isfile(self.paths.gms_root_sha1)) 142 self.assertTrue(os.path.isfile(self.paths.gms_root_license)) 143 self.assertEquals(_GetFileContent(self.paths.gms_root_license), 144 self.DEFAULT_LICENSE) 145 146 def testDownloadAlreadyUpToDate(self): 147 self.SetUpWorkdir( 148 populate_bucket=True, 149 existing_zip_sha1=self.DEFAULT_ZIP_SHA1) 150 151 status = update.main([ 152 'download', 153 '--dry-run', 154 '--bucket', self.paths.bucket, 155 '--config', self.paths.config_file, 156 '--sdk-root', self.paths.sdk_root, 157 ]) 158 159 self.assertEqual(status, 0, 'the command should have succeeded.') 160 161 # there should not be new files downloaded to sdk_root 162 self.assertFalse(os.path.isfile(os.path.join(self.paths.gms_lib, 163 'dummy_file'))) 164 self.assertFalse(os.path.isfile(self.paths.gms_root_license)) 165 166 def testDownloadAcceptedLicense(self): 167 self.SetUpWorkdir( 168 populate_bucket=True, 169 existing_license=self.DEFAULT_LICENSE) 170 171 # License already accepted, no need to type 172 status = update.main([ 173 'download', 174 '--dry-run', 175 '--bucket', self.paths.bucket, 176 '--config', self.paths.config_file, 177 '--sdk-root', self.paths.sdk_root, 178 ]) 179 180 self.assertEqual(status, 0, 'the command should have succeeded.') 181 182 # sdk_root should contain zip contents, zip sha1, license 183 self.assertTrue(os.path.isfile(os.path.join(self.paths.gms_lib, 184 'dummy_file'))) 185 self.assertTrue(os.path.isfile(self.paths.gms_root_sha1)) 186 self.assertTrue(os.path.isfile(self.paths.gms_root_license)) 187 self.assertEquals(_GetFileContent(self.paths.gms_root_license), 188 self.DEFAULT_LICENSE) 189 190 def testDownloadNewLicense(self): 191 self.SetUpWorkdir( 192 populate_bucket=True, 193 existing_license='Old license') 194 195 with _MockedInput('y'): 196 status = update.main([ 197 'download', 198 '--dry-run', 199 '--bucket', self.paths.bucket, 200 '--config', self.paths.config_file, 201 '--sdk-root', self.paths.sdk_root, 202 ]) 203 204 self.assertEqual(status, 0, 'the command should have succeeded.') 205 206 # sdk_root should contain zip contents, zip sha1, NEW license 207 self.assertTrue(os.path.isfile(os.path.join(self.paths.gms_lib, 208 'dummy_file'))) 209 self.assertTrue(os.path.isfile(self.paths.gms_root_sha1)) 210 self.assertTrue(os.path.isfile(self.paths.gms_root_license)) 211 self.assertEquals(_GetFileContent(self.paths.gms_root_license), 212 self.DEFAULT_LICENSE) 213 214 def testDownloadRefusedLicense(self): 215 self.SetUpWorkdir( 216 populate_bucket=True, 217 existing_license='Old license') 218 219 with _MockedInput('n'): 220 status = update.main([ 221 'download', 222 '--dry-run', 223 '--bucket', self.paths.bucket, 224 '--config', self.paths.config_file, 225 '--sdk-root', self.paths.sdk_root, 226 ]) 227 228 self.assertEqual(status, 0, 'the command should have succeeded.') 229 230 # there should not be new files downloaded to sdk_root 231 self.assertFalse(os.path.isfile(os.path.join(self.paths.gms_lib, 232 'dummy_file'))) 233 self.assertEquals(_GetFileContent(self.paths.gms_root_license), 234 'Old license') 235 236 def testDownloadNoAndroidSDK(self): 237 self.SetUpWorkdir( 238 populate_bucket=True, 239 existing_license='Old license') 240 241 non_existing_sdk_root = os.path.join(self.workdir, 'non_existing_sdk_root') 242 # Should not run, no typing needed 243 status = update.main([ 244 'download', 245 '--dry-run', 246 '--bucket', self.paths.bucket, 247 '--config', self.paths.config_file, 248 '--sdk-root', non_existing_sdk_root, 249 ]) 250 251 self.assertEqual(status, 0, 'the command should have succeeded.') 252 self.assertFalse(os.path.isdir(non_existing_sdk_root)) 253 254 def SetUpWorkdir(self, 255 bot_env=False, 256 config_version=DEFAULT_CONFIG_VERSION, 257 existing_license=None, 258 existing_zip_sha1=None, 259 gms_lib=False, 260 populate_bucket=False, 261 source_prop=None, 262 xml_version=None): 263 '''Prepares workdir by putting it in the specified state 264 265 Args: 266 - general 267 bot_env: sets or unsets CHROME_HEADLESS 268 269 - bucket 270 populate_bucket: boolean. Populate the bucket with a zip and license 271 file. The sha1s will be copied to the config directory 272 273 - config 274 config_version: number. Version of the current SDK. Defaults to 275 `self.DEFAULT_CONFIG_VERSION` 276 277 - sdk_root 278 existing_license: string. Create a LICENSE file setting the specified 279 text as content of the currently accepted license. 280 existing_zip_sha1: string. Create a sha1 file setting the specified 281 hash as hash of the SDK supposed to be installed 282 gms_lib: boolean. Create a dummy file in the location of the play 283 services SDK. 284 source_prop: boolean. Create a source.properties file that contains 285 the license to upload. 286 xml_version: number. Create a version.xml file with the specified 287 version that is used when uploading 288 ''' 289 self.paths = Paths(self.workdir) 290 291 # Create the main directories 292 _MakeDirs(self.paths.sdk_root) 293 _MakeDirs(self.paths.config_dir) 294 _MakeDirs(self.paths.bucket) 295 296 # is not configured via argument. 297 update.SHA1_DIRECTORY = self.paths.config_dir 298 299 os.environ['CHROME_HEADLESS'] = '1' if bot_env else '' 300 301 if config_version: 302 _MakeDirs(os.path.dirname(self.paths.config_file)) 303 with open(self.paths.config_file, 'w') as stream: 304 stream.write(('{"version_number":%d,' 305 '"version_xml_path": "res/values/version.xml"}' 306 '\n') % config_version) 307 308 if existing_license: 309 _MakeDirs(self.paths.gms_root) 310 with open(self.paths.gms_root_license, 'w') as stream: 311 stream.write(existing_license) 312 313 if existing_zip_sha1: 314 _MakeDirs(self.paths.gms_root) 315 with open(self.paths.gms_root_sha1, 'w') as stream: 316 stream.write(existing_zip_sha1) 317 318 if gms_lib: 319 _MakeDirs(self.paths.gms_lib) 320 with open(os.path.join(self.paths.gms_lib, 'dummy_file'), 'w') as stream: 321 stream.write('foo\n') 322 323 if source_prop: 324 _MakeDirs(os.path.dirname(self.paths.source_prop)) 325 with open(self.paths.source_prop, 'w') as stream: 326 stream.write('Foo=Bar\n' 327 'Pkg.License=%s\n' 328 'Baz=Fizz\n' % self.DEFAULT_LICENSE) 329 330 if populate_bucket: 331 _MakeDirs(self.paths.config_dir) 332 bucket_dir = os.path.join(self.paths.bucket, str(config_version)) 333 _MakeDirs(bucket_dir) 334 335 # TODO(dgn) should we use real sha1s? comparison with the real sha1 is 336 # done but does not do anything other than displaying a message. 337 config_license_sha1 = 'license0and0filling0to0forty0chars000000' 338 with open(self.paths.config_license_sha1, 'w') as stream: 339 stream.write(config_license_sha1) 340 341 with open(os.path.join(bucket_dir, config_license_sha1), 'w') as stream: 342 stream.write(self.DEFAULT_LICENSE) 343 344 config_zip_sha1 = self.DEFAULT_ZIP_SHA1 345 with open(self.paths.config_zip_sha1, 'w') as stream: 346 stream.write(config_zip_sha1) 347 348 pre_zip_lib = os.path.join(self.workdir, 'pre_zip_lib') 349 post_zip_lib = os.path.join(bucket_dir, config_zip_sha1) 350 _MakeDirs(pre_zip_lib) 351 with open(os.path.join(pre_zip_lib, 'dummy_file'), 'w') as stream: 352 stream.write('foo\n') 353 shutil.make_archive(post_zip_lib, 'zip', pre_zip_lib) 354 # make_archive appends .zip 355 shutil.move(post_zip_lib + '.zip', post_zip_lib) 356 357 if xml_version: 358 _MakeDirs(os.path.dirname(self.paths.xml_version)) 359 with open(self.paths.xml_version, 'w') as stream: 360 stream.write( 361 '<?xml version="1.0" encoding="utf-8"?>\n' 362 '<resources>\n' 363 ' <integer name="google_play_services_version">%d</integer>\n' 364 '</resources>\n' % xml_version) 365 366 367class Paths(object): 368 '''Declaration of the paths commonly manipulated in the tests.''' 369 370 def __init__(self, workdir): 371 self.bucket = os.path.join(workdir, 'bucket') 372 373 self.config_dir = os.path.join(workdir, 'config') 374 self.config_file = os.path.join(self.config_dir, 'config.json') 375 self.config_license_sha1 = os.path.join(self.config_dir, 'LICENSE.sha1') 376 self.config_zip_sha1 = os.path.join( 377 self.config_dir, 378 'google_play_services_library.zip.sha1') 379 380 self.sdk_root = os.path.join(workdir, 'sdk_root') 381 self.gms_root = os.path.join(self.sdk_root, 'extras', 'google', 382 'google_play_services') 383 self.gms_root_sha1 = os.path.join(self.gms_root, 384 'google_play_services_library.zip.sha1') 385 self.gms_root_license = os.path.join(self.gms_root, 'LICENSE') 386 self.source_prop = os.path.join(self.gms_root, 'source.properties') 387 self.gms_lib = os.path.join(self.gms_root, 'libproject', 388 'google-play-services_lib') 389 self.xml_version = os.path.join(self.gms_lib, 'res', 'values', 390 'version.xml') 391 392 393def _GetFileContent(file_path): 394 with open(file_path, 'r') as stream: 395 return stream.read() 396 397 398def _MakeDirs(path): 399 '''Avoids having to do the error handling everywhere.''' 400 if not os.path.exists(path): 401 os.makedirs(path) 402 403 404@contextlib.contextmanager 405def _MockedInput(typed_string): 406 '''Makes raw_input return |typed_string| while inside the context.''' 407 try: 408 original_raw_input = __builtins__.raw_input 409 __builtins__.raw_input = lambda _: typed_string 410 yield 411 finally: 412 __builtins__.raw_input = original_raw_input 413 414 415if __name__ == '__main__': 416 unittest.main() 417