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 6import os 7import re 8import subprocess 9import sys 10import tarfile 11import tempfile 12import test_server 13import unittest 14import zipfile 15 16SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 17BUILD_TOOLS_DIR = os.path.dirname(SCRIPT_DIR) 18TOOLS_DIR = os.path.join(os.path.dirname(BUILD_TOOLS_DIR), 'tools') 19 20sys.path.extend([BUILD_TOOLS_DIR, TOOLS_DIR]) 21import getos 22import manifest_util 23import oshelpers 24 25 26MANIFEST_BASENAME = 'naclsdk_manifest2.json' 27 28# Attribute '' defined outside __init__ 29# pylint: disable=W0201 30 31class SdkToolsTestCase(unittest.TestCase): 32 def tearDown(self): 33 if self.server: 34 self.server.Shutdown() 35 oshelpers.Remove(['-rf', self.basedir]) 36 37 def SetupDefault(self): 38 self.SetupWithBaseDirPrefix('sdktools') 39 40 def SetupWithBaseDirPrefix(self, basedir_prefix, tmpdir=None): 41 self.basedir = tempfile.mkdtemp(prefix=basedir_prefix, dir=tmpdir) 42 self.cache_dir = os.path.join(self.basedir, 'nacl_sdk', 'sdk_cache') 43 # We have to make sure that we build our updaters with a version that is at 44 # least as large as the version in the sdk_tools bundle. If not, update 45 # tests may fail because the "current" version (according to the sdk_cache) 46 # is greater than the version we are attempting to update to. 47 self.current_revision = self._GetSdkToolsBundleRevision() 48 self._BuildUpdater(self.basedir, self.current_revision) 49 self.manifest = self._ReadCacheManifest() 50 self.sdk_tools_bundle = self.manifest.GetBundle('sdk_tools') 51 self.server = test_server.LocalHTTPServer(self.basedir) 52 53 def _GetSdkToolsBundleRevision(self): 54 """Get the sdk_tools bundle revision. 55 We get this from the checked-in path; this is the same file that 56 build_updater uses to specify the current revision of sdk_tools.""" 57 58 manifest_filename = os.path.join(BUILD_TOOLS_DIR, 'json', 59 'naclsdk_manifest0.json') 60 manifest = manifest_util.SDKManifest() 61 manifest.LoadDataFromString(open(manifest_filename, 'r').read()) 62 return manifest.GetBundle('sdk_tools').revision 63 64 def _WriteConfig(self, config_data): 65 config_filename = os.path.join(self.cache_dir, 'naclsdk_config.json') 66 with open(config_filename, 'w') as stream: 67 stream.write(config_data) 68 69 def _WriteCacheManifest(self, manifest): 70 """Write the manifest at nacl_sdk/sdk_cache. 71 72 This is useful for faking having installed a bundle. 73 """ 74 manifest_filename = os.path.join(self.cache_dir, MANIFEST_BASENAME) 75 with open(manifest_filename, 'w') as stream: 76 stream.write(manifest.GetDataAsString()) 77 78 def _ReadCacheManifest(self): 79 """Read the manifest at nacl_sdk/sdk_cache.""" 80 manifest_filename = os.path.join(self.cache_dir, MANIFEST_BASENAME) 81 manifest = manifest_util.SDKManifest() 82 with open(manifest_filename) as stream: 83 manifest.LoadDataFromString(stream.read()) 84 return manifest 85 86 def _WriteManifest(self): 87 with open(os.path.join(self.basedir, MANIFEST_BASENAME), 'w') as stream: 88 stream.write(self.manifest.GetDataAsString()) 89 90 def _BuildUpdater(self, out_dir, revision=None): 91 build_updater_py = os.path.join(BUILD_TOOLS_DIR, 'build_updater.py') 92 cmd = [sys.executable, build_updater_py, '-o', out_dir] 93 if revision: 94 cmd.extend(['-r', str(revision)]) 95 96 process = subprocess.Popen(cmd, stdout=subprocess.PIPE) 97 _, _ = process.communicate() 98 self.assertEqual(process.returncode, 0) 99 100 def _BuildUpdaterArchive(self, rel_path, revision): 101 """Build a new sdk_tools bundle. 102 103 Args: 104 rel_path: The relative path to build the updater. 105 revision: The revision number to give to this bundle. 106 Returns: 107 A manifest_util.Archive() that points to this new bundle on the local 108 server. 109 """ 110 self._BuildUpdater(os.path.join(self.basedir, rel_path), revision) 111 112 new_sdk_tools_tgz = os.path.join(self.basedir, rel_path, 'sdk_tools.tgz') 113 with open(new_sdk_tools_tgz, 'rb') as sdk_tools_stream: 114 archive_sha1, archive_size = manifest_util.DownloadAndComputeHash( 115 sdk_tools_stream) 116 117 archive = manifest_util.Archive('all') 118 archive.url = self.server.GetURL('%s/sdk_tools.tgz' % (rel_path,)) 119 archive.checksum = archive_sha1 120 archive.size = archive_size 121 return archive 122 123 def _Run(self, args): 124 naclsdk_shell_script = os.path.join(self.basedir, 'nacl_sdk', 'naclsdk') 125 if getos.GetPlatform() == 'win': 126 naclsdk_shell_script += '.bat' 127 cmd = [naclsdk_shell_script] 128 cmd.extend(args) 129 cmd.extend(['-U', self.server.GetURL(MANIFEST_BASENAME)]) 130 process = subprocess.Popen(cmd, stdout=subprocess.PIPE) 131 stdout, _ = process.communicate() 132 try: 133 self.assertEqual(process.returncode, 0) 134 except Exception: 135 print stdout 136 raise 137 return stdout 138 139 def _RunAndExtractRevision(self): 140 stdout = self._Run(['version']) 141 match = re.search('version r(\d+)', stdout) 142 self.assertTrue(match is not None) 143 return int(match.group(1)) 144 145 146class TestSdkTools(SdkToolsTestCase): 147 def testPathHasSpaces(self): 148 """Test that running naclsdk from a path with spaces works.""" 149 self.SetupWithBaseDirPrefix('sdk tools') 150 self._WriteManifest() 151 self._RunAndExtractRevision() 152 153 154class TestBuildUpdater(SdkToolsTestCase): 155 def setUp(self): 156 self.SetupDefault() 157 158 def testUpdaterPathsAreSane(self): 159 """Test that the paths to files in nacl_sdk.zip and sdktools.tgz are 160 relative to the output directory.""" 161 nacl_sdk_zip_path = os.path.join(self.basedir, 'nacl_sdk.zip') 162 zip_stream = zipfile.ZipFile(nacl_sdk_zip_path, 'r') 163 try: 164 self.assertTrue(all(name.startswith('nacl_sdk') 165 for name in zip_stream.namelist())) 166 finally: 167 zip_stream.close() 168 169 # sdktools.tgz has no built-in directories to look for. Instead, just look 170 # for some files that must be there. 171 sdktools_tgz_path = os.path.join(self.basedir, 'sdk_tools.tgz') 172 tar_stream = tarfile.open(sdktools_tgz_path, 'r:gz') 173 try: 174 names = [m.name for m in tar_stream.getmembers()] 175 self.assertTrue('LICENSE' in names) 176 self.assertTrue('sdk_update.py' in names) 177 finally: 178 tar_stream.close() 179 180 181class TestAutoUpdateSdkTools(SdkToolsTestCase): 182 def setUp(self): 183 self.SetupDefault() 184 185 def testNoUpdate(self): 186 """Test that running naclsdk with current revision does nothing.""" 187 self._WriteManifest() 188 revision = self._RunAndExtractRevision() 189 self.assertEqual(revision, self.current_revision) 190 191 def testUpdate(self): 192 """Test that running naclsdk with a new revision will auto-update.""" 193 new_revision = self.current_revision + 1 194 archive = self._BuildUpdaterArchive('new', new_revision) 195 self.sdk_tools_bundle.RemoveAllArchivesForHostOS(archive.host_os) 196 self.sdk_tools_bundle.AddArchive(archive) 197 self.sdk_tools_bundle.revision = new_revision 198 self._WriteManifest() 199 200 revision = self._RunAndExtractRevision() 201 self.assertEqual(revision, new_revision) 202 203 def testManualUpdateIsIgnored(self): 204 """Test that attempting to manually update sdk_tools is ignored. 205 206 If the sdk_tools bundle was updated normally (i.e. the old way), it would 207 leave a sdk_tools_update folder that would then be copied over on a 208 subsequent run. This test ensures that there is no folder made. 209 """ 210 new_revision = self.current_revision + 1 211 archive = self._BuildUpdaterArchive('new', new_revision) 212 self.sdk_tools_bundle.RemoveAllArchivesForHostOS(archive.host_os) 213 self.sdk_tools_bundle.AddArchive(archive) 214 self.sdk_tools_bundle.revision = new_revision 215 self._WriteManifest() 216 217 sdk_tools_update_dir = os.path.join(self.basedir, 'nacl_sdk', 218 'sdk_tools_update') 219 self.assertFalse(os.path.exists(sdk_tools_update_dir)) 220 stdout = self._Run(['update', 'sdk_tools']) 221 self.assertTrue(stdout.find('Ignoring manual update request.') != -1) 222 self.assertFalse(os.path.exists(sdk_tools_update_dir)) 223 224 def testHelpCommand(self): 225 """Running naclsdk with -h should work. 226 227 This is a regression test for a bug where the auto-updater would remove the 228 sdk_tools directory when running "naclsdk -h". 229 """ 230 self._WriteManifest() 231 self._Run(['-h']) 232 233 234class TestAutoUpdateSdkToolsDifferentFilesystem(TestAutoUpdateSdkTools): 235 def setUp(self): 236 # On Linux (on my machine at least), /tmp is a different filesystem than 237 # the current directory. os.rename fails when the source and destination 238 # are on different filesystems. Test that case here. 239 self.SetupWithBaseDirPrefix('sdktools', tmpdir='.') 240 241 242if __name__ == '__main__': 243 sys.exit(unittest.main()) 244