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