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