Chromium Code Reviews
|
| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
|
Nirnimesh
2012/07/04 00:39:19
Why is the dir called 'install_test' if the intent
nkang
2012/07/13 23:50:50
I asked Ken about this on Tuesday. His said that i
| |
| 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 httplib | |
| 7 import logging | |
| 8 import optparse | |
| 9 import os | |
| 10 import platform | |
| 11 import shutil | |
| 12 import sys | |
| 13 import tempfile | |
| 14 import unittest | |
| 15 import urllib | |
| 16 import urlparse | |
| 17 | |
| 18 import chrome_checkout | |
| 19 import chrome_installer | |
| 20 from py_unittest_util import GTestTextTestRunner | |
| 21 | |
| 22 sys.path.append(os.path.join(os.path.pardir, 'pyautolib')) | |
| 23 | |
| 24 # This import should go after sys.path is set appropriately. | |
| 25 from fetch_prebuilt_pyauto import FetchPrebuilt | |
| 26 | |
| 27 _DOWNLOAD_DIR = [] | |
|
Nirnimesh
2012/07/04 00:39:19
Do not use globals please
nkang
2012/07/13 23:50:50
The naming scheme being used previously for downlo
| |
| 28 _INSTALLERS = [] | |
| 29 _B_FIRST_TIME = True | |
| 30 _OPTIONS = None | |
| 31 | |
| 32 | |
| 33 class InstallTest(unittest.TestCase): | |
| 34 """Test fixture for tests involving installing/updating Chrome. | |
| 35 | |
| 36 Provides an interface to install or update chrome from within a testcase, and | |
| 37 allows users to run pyauto tests using the installed version. User and system | |
| 38 level installations are supported, and either one can be used for running the | |
| 39 pyauto tests. Pyautolib files are downloaded at runtime and a PyUITest object | |
| 40 is created when Chrome is installed or updated. Users can utilize that object | |
| 41 to run updater tests. All Updater tests should derive from this class. | |
| 42 """ | |
| 43 | |
| 44 _build_iterator = None | |
| 45 _current_build = '' | |
| 46 _current_location = None | |
| 47 _dir_prefix = '__CHRBLD__' | |
| 48 _dir_iterator = None | |
| 49 _installer = None | |
| 50 _pyauto = None | |
| 51 | |
| 52 def __init__(self, methodName='runTest'): | |
| 53 global _B_FIRST_TIME | |
| 54 global _DOWNLOAD_DIR | |
| 55 unittest.TestCase.__init__(self, methodName) | |
| 56 self._platform = self._GetPlatform() | |
| 57 self._SetUp() | |
| 58 self._InitSourceFolderNames() | |
| 59 if _B_FIRST_TIME: | |
| 60 if self._installation.GetChromeInstallationType(): | |
| 61 self._DeleteBuild() | |
| 62 for build in self._builds: | |
| 63 if not self._DownloadDeps(build): | |
| 64 raise RuntimeError('Could not download dependencies.') | |
| 65 _B_FIRST_TIME = False | |
| 66 self._installer_iter = iter(_INSTALLERS) | |
| 67 self._dir_iterator = iter(_DOWNLOAD_DIR) | |
| 68 self._current_location = next(self._dir_iterator, None) | |
| 69 | |
| 70 def _SetUp(self): | |
|
Nirnimesh
2012/07/04 00:39:19
Choose a different name, not to confuse with setUp
nkang
2012/07/13 23:50:50
I chose the name _SetUp because PyUITest also has
| |
| 71 """Sets test parameters.""" | |
| 72 global _OPTIONS | |
| 73 self._url = _OPTIONS.url | |
| 74 self._builds = _OPTIONS.builds and _OPTIONS.builds.split(',') or [] | |
| 75 if not self._url or not self._builds: | |
| 76 raise RuntimeError('Please specify a valid URL and two Chrome builds.') | |
| 77 self._builds.sort() | |
| 78 self._build_iterator = iter(self._builds) | |
| 79 self._current_build = next(self._build_iterator, None) | |
| 80 self._url = self._url.endswith('/') and self._url or self._url + '/' | |
| 81 self._dir = os.path.isdir(_OPTIONS.dir) and _OPTIONS.dir or os.getcwd() | |
| 82 self._options = (_OPTIONS.options and _OPTIONS.options.replace(',', ' ') | |
| 83 or '') | |
| 84 self._install_type = ('system-level' in self._options and | |
| 85 chrome_installer.InstallationType.SYSTEM or | |
| 86 chrome_installer.InstallationType.USER) | |
| 87 self._installation = chrome_installer.Installation(self._install_type) | |
| 88 | |
| 89 def _InitSourceFolderNames(self): | |
| 90 """Sets Chrome source folder names. | |
| 91 | |
| 92 The folder names are used after checkout to confirm their existence. | |
| 93 """ | |
| 94 self._source_dirs = [os.path.join('src', 'chrome', 'test', 'functional'), | |
|
Nirnimesh
2012/07/04 00:39:19
This is a maintenance burden. Please do not do thi
nkang
2012/07/13 23:50:50
Got rid of this method. Its main purpose was to ma
| |
| 95 os.path.join('src', 'chrome', 'test', 'pyautolib'), | |
| 96 os.path.join('src', 'third_party', 'pyftpdlib'), | |
| 97 os.path.join('src', 'third_party', 'simplejson'), | |
| 98 os.path.join('src', 'third_party', 'tlslite'), | |
| 99 os.path.join('src', 'net', 'tools', 'testserver'), | |
| 100 os.path.join('src', 'third_party', 'webdriver', | |
| 101 'pylib', 'selenium')] | |
| 102 | |
| 103 def setUp(self): | |
| 104 """Called before each unittest to prepare the test fixture.""" | |
| 105 self.InstallBuild() | |
| 106 self.failIf(self._pyauto == None) | |
| 107 | |
| 108 def tearDown(self): | |
| 109 """Called at the end of each unittest to do any test related cleanup.""" | |
| 110 self._Refresh() | |
| 111 self._DeleteBuild() | |
| 112 | |
| 113 def _GetPlatform(self): | |
| 114 """Returns the platform name.""" | |
| 115 return ({'Windows': 'win', | |
| 116 'Darwin': 'mac', | |
| 117 'Linux': 'linux'}).get(platform.system()) | |
| 118 | |
| 119 def _Refresh(self): | |
| 120 """Deletes the PyUITest object and clears the modules registry.""" | |
| 121 if self._pyauto: | |
| 122 del(self._pyauto) | |
| 123 for module in ['pyauto', 'pyautolib', '_pyautolib']: | |
| 124 if module in sys.modules: | |
| 125 sys.modules.pop(module) | |
| 126 | |
| 127 def _RemovePaths(self): | |
| 128 """Restores the sys.path variable to its original state.""" | |
| 129 sys.path = list(frozenset(sys.path)) | |
| 130 if self._current_location in sys.path: | |
| 131 sys.path.remove(self._current_location) | |
| 132 if os.path.join(self._current_location, 'pyautolib') in sys.path: | |
| 133 sys.path.remove(os.path.join(self._current_location, 'pyautolib')) | |
| 134 | |
| 135 def _Install(self): | |
| 136 """Helper method that installs Chrome and creates a PyUITest object.""" | |
| 137 self._pyauto = None | |
| 138 installer_path = next(self._installer_iter, None) | |
| 139 if not installer_path: | |
| 140 raise RuntimeError('No more builds left to install.') | |
| 141 ret = chrome_installer.Install(installer_path, self._install_type, | |
| 142 self._current_build, self._options) | |
| 143 if not ret: | |
| 144 raise RuntimeError('Chrome installation for build %s failed.' % | |
| 145 self._current_build) | |
| 146 try: | |
| 147 import pyauto | |
| 148 self._pyauto = pyauto.PyUITest(methodName='runTest', | |
| 149 browser_path=os.path.dirname( | |
| 150 self._installation.GetChromeExePath())) | |
| 151 self._pyauto.suite_holder = pyauto.PyUITestSuite(['test.py']) | |
| 152 self._pyauto.setUp() | |
| 153 except ImportError, err: | |
| 154 logging.log(logging.ERROR, err) | |
| 155 self._pyauto = None | |
| 156 | |
| 157 def InstallBuild(self): | |
| 158 self._RemovePaths() | |
| 159 sys.path.insert(0, self._current_location) | |
| 160 sys.path.insert(1, os.path.join(self._current_location, 'pyautolib')) | |
| 161 self._Install() | |
| 162 | |
| 163 def _Update(self): | |
| 164 """Helper method for updating Chrome.""" | |
| 165 self._RemovePaths() | |
| 166 self._current_location = next(self._dir_iterator, None) | |
| 167 assert (self._current_location) | |
| 168 sys.path.insert(0, self._current_location) | |
| 169 sys.path.insert(1, os.path.join(self._current_location, 'pyautolib')) | |
| 170 build = next(self._build_iterator, None) | |
| 171 if not build: | |
| 172 raise RuntimeError('No more builds left to install. Following builds ' | |
| 173 'have already been installed: %r' % self._builds) | |
| 174 self._current_build = build | |
| 175 self._Install() | |
| 176 | |
| 177 def UpdateBuild(self): | |
| 178 """Installs the second Chrome build specified in the command args.""" | |
| 179 if self._pyauto: | |
| 180 self._pyauto.TearDown() | |
| 181 self._Refresh() | |
| 182 self._Update() | |
| 183 | |
| 184 def _SrcFilesExist(self, root, items): | |
| 185 """Checks if specified files/folders exist at specified 'root' folder. | |
| 186 | |
| 187 Args: | |
| 188 root: Parent folder where all the source directories reside. | |
| 189 items: List of files/folders to be verified for existence in the root. | |
| 190 | |
| 191 Returns: | |
| 192 True, if all sub-folders exist in the root, otherwise False. | |
| 193 """ | |
| 194 return all(map(lambda p: os.path.exists(p) and True or False, | |
| 195 [os.path.join(root, path) for path in items])) | |
| 196 | |
| 197 def _CheckoutSourceFiles(self, build, location): | |
| 198 """Checks out source files associated with the current build. | |
| 199 | |
| 200 Args: | |
| 201 build: Chrome release version number. | |
| 202 location: Destination where source files will be saved. | |
| 203 Returns: | |
| 204 Zero if successful, otherwise a negative value. | |
| 205 """ | |
| 206 if(not os.path.isdir(location) or not | |
| 207 self._SrcFilesExist(location, self._source_dirs)): | |
| 208 chrome_checkout.CheckOut(build, location) | |
| 209 return self._SrcFilesExist(location, self._source_dirs) | |
| 210 else: | |
| 211 return True | |
| 212 | |
| 213 def _DownloadDeps(self, build): | |
| 214 """Downloads Chrome build, source files, and chrome installer. | |
| 215 | |
| 216 Args: | |
| 217 build: Chrome build number. | |
| 218 """ | |
| 219 global _DOWNLOAD_DIR | |
| 220 ret = -1 | |
| 221 url = '%s%s/%s' % (self._url, build, self._platform) | |
| 222 download_dir = os.path.join('%s', '%s%s') % (tempfile.gettempdir(), | |
| 223 self._dir_prefix, build) | |
| 224 _DOWNLOAD_DIR.append(download_dir) | |
| 225 if not os.path.isdir(download_dir): | |
| 226 try: | |
| 227 os.mkdir(download_dir) | |
| 228 except(OSError, IOError), err: | |
| 229 logging.log(logging.ERROR, err) | |
| 230 return False | |
| 231 if not self._SrcFilesExist(download_dir, ['pyautolib.py', | |
| 232 '_pyautolib.pyd']): | |
| 233 fpb = FetchPrebuilt(url, download_dir, self._platform) | |
| 234 if fpb.DoesUrlExist(url): | |
| 235 ret = fpb.Run() | |
| 236 else: | |
| 237 ret = 0 | |
| 238 if ret == 0: | |
| 239 if self._Download('mini_installer.exe', url, download_dir): | |
| 240 _INSTALLERS.append(os.path.join(download_dir, 'mini_installer.exe')) | |
| 241 else: | |
| 242 return False | |
| 243 return self._CheckoutSourceFiles(build, download_dir) | |
| 244 return False | |
| 245 | |
| 246 def _DeleteBuild(self): | |
| 247 """Uninstalls Chrome.""" | |
| 248 global _DOWNLOAD_DIR | |
| 249 global _B_FIRST_TIME | |
| 250 ret = self._installation.UninstallChrome() | |
| 251 if not _B_FIRST_TIME: | |
| 252 if ret: | |
| 253 # Reposition iterators to the beginning of their respective lists. | |
| 254 self._build_iterator = iter(self._builds) | |
| 255 self._dir_iterator = iter(_DOWNLOAD_DIR) | |
| 256 self._current_build = next(self._build_iterator, None) | |
| 257 self._current_location = next(self._dir_iterator, None) | |
| 258 self._RemovePaths() | |
| 259 return ret | |
| 260 | |
| 261 def _DeleteDepFiles(self): | |
| 262 """Deletes Chrome related files that were downloaded for testing.""" | |
| 263 global _DOWNLOAD_DIR | |
| 264 for path in _DOWNLOAD_DIR: | |
| 265 try: | |
| 266 shutil.rmtree(path) | |
| 267 except shutil.Error, err: | |
| 268 logging.log(logging.ERROR, err) | |
| 269 return -1 | |
| 270 return 0 | |
| 271 | |
| 272 def _Download(self, dfile, url, dest=None): | |
| 273 """Downloads a file from the specified URL. | |
| 274 | |
| 275 Args: | |
| 276 dfile: Name of the file to download. | |
| 277 url: URL where the file is located. | |
| 278 dest: Location where file will be downloaded. Default is CWD. | |
| 279 | |
| 280 Returns: | |
| 281 Zero if successful, otherwise a non-zero value. | |
| 282 """ | |
| 283 filename = ((dest and os.path.exists(dest)) and os.path.join(dest, dfile) | |
| 284 or os.path.join(tempfile.gettempdir(), dfile)) | |
| 285 file_url = '%s/%s' % (url, dfile) | |
| 286 if not self._DoesUrlExist(file_url): | |
| 287 raise RuntimeError('Either the URL or the file name is invalid.') | |
| 288 try: | |
| 289 d = urllib.urlretrieve(file_url, filename) | |
| 290 except IOError, err: | |
| 291 raise err | |
| 292 return os.path.isfile(d[0]) | |
| 293 | |
| 294 def _DoesUrlExist(self, url): | |
| 295 """Checks if a URL exists. | |
| 296 | |
| 297 Args: | |
| 298 url: URL to be verified. | |
| 299 | |
| 300 Returns: | |
| 301 True if the URL exists, otherwise False. | |
| 302 """ | |
| 303 parse = urlparse.urlparse(url) | |
| 304 if parse[0] == '' or parse[1] == '': | |
| 305 return False | |
| 306 try: | |
| 307 connection = httplib.HTTPConnection(parse.netloc) | |
| 308 connection.request('HEAD', parse.path) | |
| 309 response = connection.getresponse() | |
| 310 except (socket.error, socket.gaierror): | |
| 311 return False | |
| 312 finally: | |
| 313 connection.close() | |
| 314 if response.status == 302 or response.status == 301: | |
| 315 return self._DoesUrlExist(response.getheader('location')) | |
| 316 return response.status == 200 | |
| 317 | |
| 318 | |
| 319 class Main(object): | |
| 320 """Main program for running Updater tests.""" | |
| 321 | |
| 322 _tests_file = 'PYAUTO_TESTS' | |
| 323 _mod_path = sys.argv[0] | |
| 324 _pyauto_doc_url = 'http://dev.chromium.org/developers/testing/pyauto' | |
| 325 | |
| 326 def __init__(self): | |
| 327 self._ParseArgs() | |
| 328 self._Run() | |
| 329 | |
| 330 def _GetUnitTests(self): | |
| 331 """Returns a list of unittests from the calling script.""" | |
| 332 mod_name = [os.path.splitext(os.path.basename(self._mod_path))[0]] | |
| 333 if os.path.dirname(self._mod_path) not in sys.path: | |
| 334 sys.path.append(os.path.dirname(self._mod_path)) | |
| 335 return unittest.defaultTestLoader.loadTestsFromNames(mod_name) | |
| 336 | |
| 337 def _Run(self): | |
| 338 """Runs the unit tests.""" | |
| 339 tests = self._GetUnitTests() | |
| 340 result = GTestTextTestRunner(verbosity=1).run(tests) | |
| 341 del(tests) | |
| 342 if not result.wasSuccessful(): | |
| 343 print >>sys.stderr, ('Tests can be disabled by editing %s. Ref: %s' | |
| 344 % (self._tests_file, self._pyauto_doc_url)) | |
| 345 sys.exit(1) | |
| 346 else: | |
| 347 sys.exit(0) | |
| 348 | |
| 349 def _ParseArgs(self): | |
| 350 """Parses command line arguments.""" | |
| 351 global _OPTIONS | |
| 352 parser = optparse.OptionParser() | |
| 353 parser.add_option( | |
| 354 '-b', '--builds', type='string', default='', dest='builds', | |
| 355 help='Specifies the two (or more) builds needed for testing.') | |
| 356 parser.add_option( | |
| 357 '-u', '--url', type='string', default='', dest='url', | |
| 358 help='Specifies the chrome-master2 url, without the build number.') | |
| 359 parser.add_option( | |
| 360 '-d', '--dir', type='string', default=os.getcwd(), | |
| 361 help='Specifies directory where the installer will be downloaded.') | |
| 362 parser.add_option( | |
| 363 '-o', '--options', type='string', default='', | |
| 364 help='Specifies any additional Chrome options (i.e. --system-level).') | |
| 365 opts, args = parser.parse_args() | |
| 366 _OPTIONS = opts | |
| OLD | NEW |