Home | History | Annotate | Download | only in tests
      1 #!/usr/bin/env python
      2 # Copyright (c) 2012 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 import os
      7 import sys
      8 import re
      9 import tarfile
     10 import tempfile
     11 import unittest
     12 from sdktools_test import SdkToolsTestCase
     13 
     14 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
     15 BUILD_TOOLS_DIR = os.path.dirname(SCRIPT_DIR)
     16 TOOLS_DIR = os.path.join(os.path.dirname(BUILD_TOOLS_DIR), 'tools')
     17 
     18 sys.path.extend([BUILD_TOOLS_DIR, TOOLS_DIR])
     19 import manifest_util
     20 import oshelpers
     21 
     22 
     23 class TestCommands(SdkToolsTestCase):
     24   def setUp(self):
     25     self.SetupDefault()
     26 
     27   def _AddDummyBundle(self, manifest, bundle_name):
     28     bundle = manifest_util.Bundle(bundle_name)
     29     bundle.revision = 1337
     30     bundle.version = 23
     31     bundle.description = bundle_name
     32     bundle.stability = 'beta'
     33     bundle.recommended = 'no'
     34     bundle.repath = bundle_name
     35     archive = self._MakeDummyArchive(bundle_name)
     36     bundle.AddArchive(archive)
     37     manifest.SetBundle(bundle)
     38 
     39     # Need to get the bundle from the manifest -- it doesn't use the one we
     40     # gave it.
     41     return manifest.GetBundle(bundle_name)
     42 
     43   def _MakeDummyArchive(self, bundle_name, tarname=None, filename='dummy.txt'):
     44     tarname = (tarname or bundle_name) + '.tar.bz2'
     45     temp_dir = tempfile.mkdtemp(prefix='archive')
     46     try:
     47       dummy_path = os.path.join(temp_dir, filename)
     48       with open(dummy_path, 'w') as stream:
     49         stream.write('Dummy stuff for %s' % bundle_name)
     50 
     51       # Build the tarfile directly into the server's directory.
     52       tar_path = os.path.join(self.basedir, tarname)
     53       tarstream = tarfile.open(tar_path, 'w:bz2')
     54       try:
     55         tarstream.add(dummy_path, os.path.join(bundle_name, filename))
     56       finally:
     57         tarstream.close()
     58 
     59       with open(tar_path, 'rb') as archive_stream:
     60         sha1, size = manifest_util.DownloadAndComputeHash(archive_stream)
     61 
     62       archive = manifest_util.Archive(manifest_util.GetHostOS())
     63       archive.url = self.server.GetURL(os.path.basename(tar_path))
     64       archive.size = size
     65       archive.checksum = sha1
     66       return archive
     67     finally:
     68       oshelpers.Remove(['-rf', temp_dir])
     69 
     70   def testInfoBasic(self):
     71     """The info command should display information about the given bundle."""
     72     self._WriteManifest()
     73     output = self._Run(['info', 'sdk_tools'])
     74     # Make sure basic information is there
     75     bundle = self.manifest.GetBundle('sdk_tools')
     76     archive = bundle.GetHostOSArchive();
     77     self.assertTrue(bundle.name in output)
     78     self.assertTrue(bundle.description in output)
     79     self.assertTrue(str(bundle.revision) in output)
     80     self.assertTrue(str(archive.size) in output)
     81     self.assertTrue(archive.checksum in output)
     82     self.assertTrue(bundle.stability in output)
     83 
     84   def testInfoUnknownBundle(self):
     85     """The info command should notify the user of unknown bundles."""
     86     self._WriteManifest()
     87     bogus_bundle = 'foobar'
     88     output = self._Run(['info', bogus_bundle])
     89     self.assertTrue(re.search(r'[uU]nknown', output))
     90     self.assertTrue(bogus_bundle in output)
     91 
     92   def testInfoMultipleBundles(self):
     93     """The info command should support listing multiple bundles."""
     94     self._AddDummyBundle(self.manifest, 'pepper_23')
     95     self._AddDummyBundle(self.manifest, 'pepper_24')
     96     self._WriteManifest()
     97     output = self._Run(['info', 'pepper_23', 'pepper_24'])
     98     self.assertTrue('pepper_23' in output)
     99     self.assertTrue('pepper_24' in output)
    100     self.assertFalse(re.search(r'[uU]nknown', output))
    101 
    102   def testInfoMultipleArchives(self):
    103     """The info command should display multiple archives."""
    104     bundle = self._AddDummyBundle(self.manifest, 'pepper_26')
    105     archive2 = self._MakeDummyArchive('pepper_26', tarname='pepper_26_more',
    106                                       filename='dummy2.txt')
    107     archive2.host_os = 'all'
    108     bundle.AddArchive(archive2)
    109     self._WriteManifest()
    110     output = self._Run(['info', 'pepper_26'])
    111     self.assertTrue('pepper_26' in output)
    112     self.assertTrue('pepper_26_more' in output)
    113 
    114   def testListBasic(self):
    115     """The list command should display basic information about remote
    116     bundles."""
    117     self._WriteManifest()
    118     output = self._Run(['list'])
    119     self.assertTrue(re.search('I.*?sdk_tools.*?stable', output, re.MULTILINE))
    120     # This line is important (it's used by the updater to determine if the
    121     # sdk_tools bundle needs to be updated), so let's be explicit.
    122     self.assertTrue('All installed bundles are up-to-date.')
    123 
    124   def testListMultiple(self):
    125     """The list command should display multiple bundles."""
    126     self._AddDummyBundle(self.manifest, 'pepper_23')
    127     self._WriteManifest()
    128     output = self._Run(['list'])
    129     # Added pepper_23 to the remote manifest not the local manifest, so it
    130     # shouldn't be installed.
    131     self.assertTrue(re.search('^[^I]*pepper_23', output, re.MULTILINE))
    132     self.assertTrue('sdk_tools' in output)
    133 
    134   def testListWithRevision(self):
    135     """The list command should display the revision, if desired."""
    136     self._AddDummyBundle(self.manifest, 'pepper_23')
    137     self._WriteManifest()
    138     output = self._Run(['list', '-r'])
    139     self.assertTrue(re.search('pepper_23.*?r1337', output))
    140 
    141   def testListWithUpdatedRevision(self):
    142     """The list command should display when there is an update available."""
    143     p23bundle = self._AddDummyBundle(self.manifest, 'pepper_23')
    144     self._WriteCacheManifest(self.manifest)
    145     # Modify the remote manifest to have a newer revision.
    146     p23bundle.revision += 1
    147     self._WriteManifest()
    148     output = self._Run(['list', '-r'])
    149     # We should see a display like this:  I* pepper_23 (r1337 -> r1338)
    150     # The star indicates the bundle has an update.
    151     self.assertTrue(re.search('I\*\s+pepper_23.*?r1337.*?r1338', output))
    152 
    153   def testListLocalVersionNotOnRemote(self):
    154     """The list command should tell the user if they have a bundle installed
    155     that doesn't exist in the remote manifest."""
    156     self._WriteManifest()
    157     p23bundle = self._AddDummyBundle(self.manifest, 'pepper_23')
    158     self._WriteCacheManifest(self.manifest)
    159     output = self._Run(['list', '-r'])
    160     message = 'Bundles installed locally that are not available remotely:'
    161     message_loc = output.find(message)
    162     self.assertNotEqual(message_loc, -1)
    163     # Make sure pepper_23 is listed after the message above.
    164     self.assertTrue('pepper_23' in output[message_loc:])
    165 
    166   def testSources(self):
    167     """The sources command should allow adding/listing/removing of sources.
    168     When a source is added, it will provide an additional set of bundles."""
    169     other_manifest = manifest_util.SDKManifest()
    170     self._AddDummyBundle(other_manifest, 'naclmono_23')
    171     with open(os.path.join(self.basedir, 'source.json'), 'w') as stream:
    172       stream.write(other_manifest.GetDataAsString())
    173 
    174     source_json_url = self.server.GetURL('source.json')
    175     self._WriteManifest()
    176     output = self._Run(['sources', '--list'])
    177     self.assertTrue('No external sources installed.' in output)
    178     output = self._Run(['sources', '--add', source_json_url])
    179     output = self._Run(['sources', '--list'])
    180     self.assertTrue(source_json_url in output)
    181 
    182     # Should be able to get info about that bundle.
    183     output = self._Run(['info', 'naclmono_23'])
    184     self.assertTrue('Unknown bundle' not in output)
    185 
    186     self._Run(['sources', '--remove', source_json_url])
    187     output = self._Run(['sources', '--list'])
    188     self.assertTrue('No external sources installed.' in output)
    189 
    190   def testUpdateBasic(self):
    191     """The update command should install the contents of a bundle to the SDK."""
    192     self._AddDummyBundle(self.manifest, 'pepper_23')
    193     self._WriteManifest()
    194     self._Run(['update', 'pepper_23'])
    195     self.assertTrue(os.path.exists(
    196         os.path.join(self.basedir, 'nacl_sdk', 'pepper_23', 'dummy.txt')))
    197 
    198   def testUpdateInCacheButDirectoryRemoved(self):
    199     """The update command should update if the bundle directory does not exist,
    200     even if the bundle is already in the cache manifest."""
    201     self._AddDummyBundle(self.manifest, 'pepper_23')
    202     self._WriteCacheManifest(self.manifest)
    203     self._WriteManifest()
    204     self._Run(['update', 'pepper_23'])
    205     self.assertTrue(os.path.exists(
    206         os.path.join(self.basedir, 'nacl_sdk', 'pepper_23', 'dummy.txt')))
    207 
    208   def testUpdateNoNewVersion(self):
    209     """The update command should do nothing if the bundle is already up-to-date.
    210     """
    211     self._AddDummyBundle(self.manifest, 'pepper_23')
    212     self._WriteManifest()
    213     self._Run(['update', 'pepper_23'])
    214     output = self._Run(['update', 'pepper_23'])
    215     self.assertTrue('is already up-to-date.' in output)
    216 
    217   def testUpdateWithNewVersion(self):
    218     """The update command should update to a new version if it exists."""
    219     bundle = self._AddDummyBundle(self.manifest, 'pepper_23')
    220     self._WriteManifest()
    221     self._Run(['update', 'pepper_23'])
    222 
    223     bundle.revision += 1
    224     self._WriteManifest()
    225     output = self._Run(['update', 'pepper_23'])
    226     self.assertTrue('already exists, but has an update available' in output)
    227 
    228     # Now update using --force.
    229     output = self._Run(['update', 'pepper_23', '--force'])
    230     self.assertTrue('Updating bundle' in output)
    231 
    232     cache_manifest = self._ReadCacheManifest()
    233     num_archives = len(cache_manifest.GetBundle('pepper_23').GetArchives())
    234     self.assertEqual(num_archives, 1)
    235 
    236   def testUpdateUnknownBundles(self):
    237     """The update command should ignore unknown bundles and notify the user."""
    238     self._WriteManifest()
    239     output = self._Run(['update', 'foobar'])
    240     self.assertTrue('unknown bundle' in output)
    241 
    242   def testUpdateRecommended(self):
    243     """The update command should update only recommended bundles when run
    244     without args.
    245     """
    246     bundle_25 = self._AddDummyBundle(self.manifest, 'pepper_25')
    247     bundle_25.recommended = 'no'
    248     bundle_26 = self._AddDummyBundle(self.manifest, 'pepper_26')
    249     bundle_26.recommended = 'yes'
    250 
    251     self._WriteManifest()
    252     output = self._Run(['update'])
    253 
    254     # Should not try to update sdk_tools (even though it is recommended)
    255     self.assertTrue('Ignoring manual update request.' not in output)
    256     self.assertFalse(os.path.exists(
    257         os.path.join(self.basedir, 'nacl_sdk', 'pepper_25')))
    258     self.assertTrue(os.path.exists(
    259         os.path.join(self.basedir, 'nacl_sdk', 'pepper_26', 'dummy.txt')))
    260 
    261   def testUpdateCanary(self):
    262     """The update command should create the correct directory name for repath'd
    263     bundles.
    264     """
    265     bundle = self._AddDummyBundle(self.manifest, 'pepper_26')
    266     bundle.name = 'pepper_canary'
    267     self._WriteManifest()
    268     output = self._Run(['update', 'pepper_canary'])
    269     self.assertTrue(os.path.exists(
    270         os.path.join(self.basedir, 'nacl_sdk', 'pepper_canary', 'dummy.txt')))
    271 
    272   def testUpdateMultiArchive(self):
    273     """The update command should include download/untar multiple archives
    274     specified in the bundle.
    275     """
    276     bundle = self._AddDummyBundle(self.manifest, 'pepper_26')
    277     archive2 = self._MakeDummyArchive('pepper_26', tarname='pepper_26_more',
    278                                       filename='dummy2.txt')
    279     archive2.host_os = 'all'
    280     bundle.AddArchive(archive2)
    281     self._WriteManifest()
    282     output = self._Run(['update', 'pepper_26'])
    283     self.assertTrue(os.path.exists(
    284         os.path.join(self.basedir, 'nacl_sdk', 'pepper_26', 'dummy.txt')))
    285     self.assertTrue(os.path.exists(
    286         os.path.join(self.basedir, 'nacl_sdk', 'pepper_26', 'dummy2.txt')))
    287 
    288   def testUpdateBadSize(self):
    289     """If an archive has a bad size, print an error.
    290     """
    291     bundle = self._AddDummyBundle(self.manifest, 'pepper_26')
    292     archive = bundle.GetHostOSArchive();
    293     archive.size = -1
    294     self._WriteManifest()
    295     stdout = self._Run(['update', 'pepper_26'], expect_error=True)
    296     self.assertTrue('Size mismatch' in stdout)
    297 
    298   def testUpdateBadSHA(self):
    299     """If an archive has a bad SHA, print an error.
    300     """
    301     bundle = self._AddDummyBundle(self.manifest, 'pepper_26')
    302     archive = bundle.GetHostOSArchive();
    303     archive.checksum = 0
    304     self._WriteManifest()
    305     stdout = self._Run(['update', 'pepper_26'], expect_error=True)
    306     self.assertTrue('SHA1 checksum mismatch' in stdout)
    307 
    308   def testUninstall(self):
    309     """The uninstall command should remove the installed bundle, if it
    310     exists.
    311     """
    312     # First install the bundle.
    313     self._AddDummyBundle(self.manifest, 'pepper_23')
    314     self._WriteManifest()
    315     output = self._Run(['update', 'pepper_23'])
    316     self.assertTrue(os.path.exists(
    317         os.path.join(self.basedir, 'nacl_sdk', 'pepper_23', 'dummy.txt')))
    318 
    319     # Now remove it.
    320     self._Run(['uninstall', 'pepper_23'])
    321     self.assertFalse(os.path.exists(
    322         os.path.join(self.basedir, 'nacl_sdk', 'pepper_23')))
    323 
    324     # The bundle should not be marked as installed.
    325     output = self._Run(['list'])
    326     self.assertTrue(re.search('^[^I]*pepper_23', output, re.MULTILINE))
    327 
    328   def testReinstall(self):
    329     """The reinstall command should remove, then install, the specified
    330     bundles.
    331     """
    332     # First install the bundle.
    333     self._AddDummyBundle(self.manifest, 'pepper_23')
    334     self._WriteManifest()
    335     output = self._Run(['update', 'pepper_23'])
    336     dummy_txt = os.path.join(self.basedir, 'nacl_sdk', 'pepper_23', 'dummy.txt')
    337     self.assertTrue(os.path.exists(dummy_txt))
    338     with open(dummy_txt) as f:
    339       self.assertEqual(f.read(), 'Dummy stuff for pepper_23')
    340 
    341     # Change some files.
    342     foo_txt = os.path.join(self.basedir, 'nacl_sdk', 'pepper_23', 'foo.txt')
    343     with open(foo_txt, 'w') as f:
    344       f.write('Another dummy file. This one is not part of the bundle.')
    345     with open(dummy_txt, 'w') as f:
    346       f.write('changed dummy.txt')
    347 
    348     # Reinstall the bundle.
    349     self._Run(['reinstall', 'pepper_23'])
    350 
    351     self.assertFalse(os.path.exists(foo_txt))
    352     self.assertTrue(os.path.exists(dummy_txt))
    353     with open(dummy_txt) as f:
    354       self.assertEqual(f.read(), 'Dummy stuff for pepper_23')
    355 
    356     cache_manifest = self._ReadCacheManifest()
    357     num_archives = len(cache_manifest.GetBundle('pepper_23').GetArchives())
    358     self.assertEqual(num_archives, 1)
    359 
    360   def testReinstallWithDuplicatedArchives(self):
    361     """The reinstall command should only use the most recent archive if there
    362     are duplicated archives.
    363 
    364     NOTE: There was a bug where the sdk_cache/naclsdk_manifest2.json file was
    365     duplicating archives from different revisions. Make sure that reinstall
    366     ignores old archives in the bundle.
    367     """
    368     # First install the bundle.
    369     self._AddDummyBundle(self.manifest, 'pepper_23')
    370     self._WriteManifest()
    371     self._Run(['update', 'pepper_23'])
    372 
    373     manifest = self._ReadCacheManifest()
    374     bundle = manifest.GetBundle('pepper_23')
    375     self.assertEqual(len(bundle.GetArchives()), 1)
    376 
    377     # Now add a bogus duplicate archive
    378     archive2 = self._MakeDummyArchive('pepper_23', tarname='pepper_23',
    379                                       filename='dummy2.txt')
    380     bundle.AddArchive(archive2)
    381     self._WriteCacheManifest(manifest)
    382 
    383     output = self._Run(['reinstall', 'pepper_23'])
    384     # When updating just one file, there is no (file 1/2 - "...") output.
    385     self.assertFalse('file 1/' in output)
    386     # Should be using the last archive.
    387     self.assertFalse(os.path.exists(
    388         os.path.join(self.basedir, 'nacl_sdk', 'pepper_23', 'dummy.txt')))
    389     self.assertTrue(os.path.exists(
    390         os.path.join(self.basedir, 'nacl_sdk', 'pepper_23', 'dummy2.txt')))
    391 
    392   def testReinstallDoesntUpdate(self):
    393     """The reinstall command should not update a bundle that has an update."""
    394     # First install the bundle.
    395     bundle = self._AddDummyBundle(self.manifest, 'pepper_23')
    396     self._WriteManifest()
    397     self._Run(['update', 'pepper_23'])
    398     dummy_txt = os.path.join(self.basedir, 'nacl_sdk', 'pepper_23', 'dummy.txt')
    399     self.assertTrue(os.path.exists(dummy_txt))
    400     with open(dummy_txt) as f:
    401       self.assertEqual(f.read(), 'Dummy stuff for pepper_23')
    402 
    403     # Update the revision.
    404     bundle.revision += 1
    405     self._WriteManifest()
    406 
    407     # Change the file.
    408     foo_txt = os.path.join(self.basedir, 'nacl_sdk', 'pepper_23', 'foo.txt')
    409     with open(dummy_txt, 'w') as f:
    410       f.write('changed dummy.txt')
    411 
    412     # Reinstall.
    413     self._Run(['reinstall', 'pepper_23'])
    414 
    415     # The data has been reinstalled.
    416     self.assertTrue(os.path.exists(dummy_txt))
    417     with open(dummy_txt) as f:
    418       self.assertEqual(f.read(), 'Dummy stuff for pepper_23')
    419 
    420     # ... but the version hasn't been updated.
    421     output = self._Run(['list', '-r'])
    422     self.assertTrue(re.search('I\*\s+pepper_23.*?r1337.*?r1338', output))
    423 
    424   def testArchiveCacheBasic(self):
    425     """Downloaded archives should be stored in the cache by default."""
    426     self._AddDummyBundle(self.manifest, 'pepper_23')
    427     self._WriteManifest()
    428     self._Run(['update', 'pepper_23'])
    429     archive_cache = os.path.join(self.cache_dir, 'archives')
    430     cache_contents = os.listdir(archive_cache)
    431     self.assertEqual(cache_contents, ['pepper_23'])
    432     cache_contents = os.listdir(os.path.join(archive_cache, 'pepper_23'))
    433     self.assertEqual(cache_contents, ['pepper_23.tar.bz2'])
    434 
    435   def testArchiveCacheEviction(self):
    436     archive_cache = os.path.join(self.cache_dir, 'archives')
    437     self._AddDummyBundle(self.manifest, 'pepper_23')
    438     self._AddDummyBundle(self.manifest, 'pepper_22')
    439     self._WriteManifest()
    440 
    441     # First install pepper_23
    442     self._Run(['update', 'pepper_23'])
    443     archive = os.path.join(archive_cache, 'pepper_23', 'pepper_23.tar.bz2')
    444     archive_size = os.path.getsize(archive)
    445 
    446     # Set the mtime on the pepper_23 bundle to be a few seconds in the past.
    447     # This is needed so that the two bundles don't end up with the same
    448     # timestamp which can happen on systems that don't report sub-second
    449     # timestamps.
    450     atime = os.path.getatime(archive)
    451     mtime = os.path.getmtime(archive)
    452     os.utime(archive, (atime, mtime-10))
    453 
    454     # Set cache limit to size of pepper archive * 1.5
    455     self._WriteConfig('{ "cache_max": %d }' % int(archive_size * 1.5))
    456 
    457     # Now install pepper_22, which should cause pepper_23 to be evicted
    458     self._Run(['update', 'pepper_22'])
    459     cache_contents = os.listdir(archive_cache)
    460     self.assertEqual(cache_contents, ['pepper_22'])
    461 
    462   def testArchiveCacheZero(self):
    463     """Archives should not be cached when cache_max is zero."""
    464     self._AddDummyBundle(self.manifest, 'pepper_23')
    465     self._WriteConfig('{ "cache_max": 0 }')
    466     self._AddDummyBundle(self.manifest, 'pepper_23')
    467     self._WriteManifest()
    468     self._Run(['update', 'pepper_23'])
    469     archive_cache = os.path.join(self.cache_dir, 'archives')
    470     # Archive folder should be completely remove by cache cleanup
    471     self.assertFalse(os.path.exists(archive_cache))
    472 
    473 if __name__ == '__main__':
    474   unittest.main()
    475