Chromium Code Reviews
|
| OLD | NEW |
|---|---|
| (Empty) | |
| 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. | |
|
Nirnimesh
2012/08/09 21:32:48
docstring.
Give an example test. Look at the top
nkang
2012/08/16 23:46:24
Added a docstring and included instructions on how
| |
| 5 | |
| 6 import httplib | |
| 7 import logging | |
| 8 import optparse | |
| 9 import os | |
| 10 import platform | |
| 11 import shutil | |
| 12 import stat | |
| 13 import sys | |
| 14 import tempfile | |
| 15 import unittest | |
| 16 import urllib | |
| 17 import urlparse | |
| 18 | |
| 19 import chrome_checkout | |
| 20 import chrome_installer | |
| 21 from chrome_installer import ChromeInstallation | |
| 22 | |
| 23 sys.path.append(os.path.join(os.path.pardir, 'pyautolib')) | |
| 24 | |
| 25 # This import should go after sys.path is set appropriately. | |
| 26 from fetch_prebuilt_pyauto import FetchPrebuilt | |
| 27 from pyauto_utils import GTestTextTestRunner | |
| 28 | |
| 29 _OPTIONS = None | |
|
Nirnimesh
2012/08/09 21:32:48
Do not use globals. What you're doing can easily b
nkang
2012/08/16 23:46:24
I actually did it this way because I was trying to
Nirnimesh
2012/08/22 07:06:34
globals should not be used unless absolutely neces
nkang
2012/08/24 22:45:26
Per our conversation, I created a 'staticmethod' i
| |
| 30 | |
| 31 | |
| 32 class InstallTest(unittest.TestCase): | |
| 33 """Test fixture for tests involving installing/updating Chrome. | |
| 34 | |
| 35 Provides an interface to install or update chrome from within a testcase, and | |
| 36 allows users to run pyauto tests using the installed version. User and system | |
| 37 level installations are supported, and either one can be used for running the | |
| 38 pyauto tests. Pyautolib files are downloaded at runtime and a PyUITest object | |
| 39 is created when Chrome is installed or updated. Users can utilize that object | |
| 40 to run updater tests. All Updater tests should derive from this class. | |
| 41 """ | |
| 42 | |
| 43 _build_iterator = None | |
| 44 _current_build = '' | |
| 45 _current_location = '' | |
| 46 _dir_prefix = '__CHRBLD__' | |
|
Nirnimesh
2012/08/09 21:32:48
why the mystique name?
nkang
2012/08/16 23:46:24
We have a particular naming scheme for folders whe
Nirnimesh
2012/08/22 07:06:34
Declare this convention somewhere.
| |
| 47 _dir_iterator = None | |
| 48 _installation = None | |
| 49 _installer_name = 'mini_installer.exe' | |
|
Nirnimesh
2012/08/09 21:32:48
So all this is win only? Where did you declare/che
nkang
2012/08/16 23:46:24
Updated the docstring so it states that only Windo
| |
| 50 _pyauto = None | |
| 51 _installers = [] | |
| 52 _download_dirs = [] | |
| 53 | |
| 54 def __init__(self, methodName='runTest'): | |
| 55 unittest.TestCase.__init__(self, methodName) | |
| 56 self._platform = self._GetPlatform() | |
| 57 self._Initialize() | |
| 58 current_build = ChromeInstallation.GetCurrent() | |
| 59 if current_build: | |
| 60 current_build.Uninstall() | |
| 61 for build in self._builds: | |
| 62 if not self._DownloadDeps(build): | |
|
Nirnimesh
2012/08/09 21:32:48
Do you want the downloading to be done separately
nkang
2012/08/16 23:46:24
No, the downloading will occur only once. This is
| |
| 63 raise RuntimeError('Could not download dependencies.') | |
| 64 self._installer_iter = iter(self._installers) | |
| 65 self._dir_iterator = iter(self._download_dirs) | |
| 66 | |
| 67 def _Initialize(self): | |
| 68 """Sets test parameters.""" | |
| 69 global _OPTIONS | |
| 70 self._url = _OPTIONS.url | |
| 71 self._builds = _OPTIONS.builds and _OPTIONS.builds.split(',') or [] | |
| 72 if not self._url or not self._builds: | |
| 73 raise RuntimeError('Please specify a valid URL and two Chrome builds.') | |
| 74 self._builds.sort() | |
| 75 self._url = self._url.endswith('/') and self._url or self._url + '/' | |
| 76 self._dir = os.path.isdir(_OPTIONS.dir) and _OPTIONS.dir or os.getcwd() | |
| 77 self._options = (_OPTIONS.options and _OPTIONS.options.replace(',', ' ') | |
| 78 or '') | |
| 79 self._install_type = ('system-level' in self._options and | |
| 80 chrome_installer.InstallationType.SYSTEM or | |
| 81 chrome_installer.InstallationType.USER) | |
| 82 self._build_iterator = iter(self._builds) | |
| 83 self._current_build = next(self._build_iterator, None) | |
| 84 | |
| 85 def setUp(self): | |
| 86 """Called before each unittest to prepare the test fixture.""" | |
| 87 self.InstallBuild() | |
| 88 self.failIf(self._pyauto == None) | |
|
Nirnimesh
2012/08/09 21:32:48
call parent's setup
nkang
2012/08/16 23:46:24
Sorry, I didn't fully understand this comment. The
Nirnimesh
2012/08/22 07:06:34
The fact that the parent does nothing is irrelevan
| |
| 89 | |
| 90 def tearDown(self): | |
| 91 """Called at the end of each unittest to do any test related cleanup.""" | |
| 92 self._Refresh() | |
| 93 self._DeleteBuild() | |
|
Nirnimesh
2012/08/09 21:32:48
call parent's tearDown
nkang
2012/08/16 23:46:24
The parent class of InstallTest is unittest.TestCa
| |
| 94 | |
| 95 def _GetPlatform(self): | |
| 96 """Returns the platform name.""" | |
| 97 return ({'Windows': 'win', | |
| 98 'Darwin': 'mac', | |
| 99 'Linux': 'linux'}).get(platform.system()) | |
| 100 | |
| 101 def _Refresh(self): | |
|
Nirnimesh
2012/08/09 21:32:48
Refresh? It's not clear from the method name that
nkang
2012/08/16 23:46:24
Renamed to _ClearModulesRegistry.
| |
| 102 """Deletes the PyUITest object and clears the modules registry.""" | |
| 103 if self._pyauto: | |
| 104 del(self._pyauto) | |
|
Nirnimesh
2012/08/09 21:32:48
remove parens
nkang
2012/08/16 23:46:24
Done.
| |
| 105 for module in ['pyauto', 'pyautolib', '_pyautolib']: | |
| 106 if module in sys.modules: | |
| 107 sys.modules.pop(module) | |
| 108 | |
| 109 def _RemovePaths(self): | |
| 110 """Restores the sys.path variable to its original state.""" | |
| 111 sys.path = list(frozenset(sys.path)) | |
| 112 if self._current_location: | |
| 113 if self._current_location in sys.path: | |
| 114 sys.path.remove(self._current_location) | |
| 115 if os.path.join(self._current_location, 'pyautolib') in sys.path: | |
| 116 sys.path.remove(os.path.join(self._current_location, 'pyautolib')) | |
| 117 | |
| 118 def _Install(self): | |
| 119 """Helper method that installs Chrome and creates a PyUITest object.""" | |
| 120 self._pyauto = None | |
| 121 installer_path = next(self._installer_iter, None) | |
| 122 if not installer_path: | |
| 123 raise RuntimeError('No more builds left to install.') | |
| 124 self._installation = chrome_installer.Install(installer_path, | |
| 125 self._install_type, | |
| 126 self._current_build, | |
| 127 self._options) | |
| 128 try: | |
| 129 import pyauto | |
| 130 self._pyauto = pyauto.PyUITest(methodName='runTest', | |
| 131 browser_path=os.path.dirname( | |
| 132 self._installation.GetExePath())) | |
| 133 self._pyauto.suite_holder = pyauto.PyUITestSuite(['test.py']) | |
|
Nirnimesh
2012/08/09 21:32:48
[] would do I think
nkang
2012/08/16 23:46:24
Yes, it did. Got rid of the arbitrary file name.
| |
| 134 self._pyauto.setUp() | |
| 135 except ImportError, err: | |
| 136 logging.log(logging.ERROR, err) | |
| 137 self._pyauto = None | |
| 138 | |
| 139 def InstallBuild(self): | |
| 140 self._current_location = next(self._dir_iterator, None) | |
| 141 sys.path.insert(0, self._current_location) | |
| 142 sys.path.insert(1, os.path.join(self._current_location, 'pyautolib')) | |
| 143 self._Install() | |
| 144 | |
| 145 def _Update(self): | |
| 146 """Helper method for updating Chrome.""" | |
| 147 self._RemovePaths() | |
| 148 self._current_location = next(self._dir_iterator, None) | |
| 149 assert (self._current_location) | |
| 150 sys.path.insert(0, self._current_location) | |
| 151 sys.path.insert(1, os.path.join(self._current_location, 'pyautolib')) | |
| 152 build = next(self._build_iterator, None) | |
| 153 if not build: | |
| 154 raise RuntimeError('No more builds left to install. Following builds ' | |
| 155 'have already been installed: %r' % self._builds) | |
| 156 self._current_build = build | |
| 157 self._Install() | |
| 158 | |
| 159 def UpdateBuild(self): | |
| 160 """Installs the second Chrome build specified in the command args.""" | |
| 161 if self._pyauto: | |
| 162 self._pyauto.TearDown() | |
| 163 self._Refresh() | |
| 164 self._Update() | |
| 165 | |
| 166 def _SrcFilesExist(self, root, items): | |
| 167 """Checks if specified files/folders exist at specified 'root' folder. | |
| 168 | |
| 169 Args: | |
| 170 root: Parent folder where all the source directories reside. | |
| 171 items: List of files/folders to be verified for existence in the root. | |
| 172 | |
| 173 Returns: | |
| 174 True, if all sub-folders exist in the root, otherwise False. | |
| 175 """ | |
| 176 return all(map(lambda p: os.path.exists(p) and True or False, | |
| 177 [os.path.join(root, path) for path in items])) | |
| 178 | |
| 179 def _CheckoutSourceFiles(self, build, location): | |
| 180 """Checks out source files associated with the current build. | |
| 181 | |
| 182 Args: | |
| 183 build: Chrome release version number. | |
| 184 location: Destination where source files will be saved. | |
| 185 Returns: | |
| 186 Zero if successful, otherwise a negative value. | |
| 187 """ | |
| 188 try: | |
| 189 chrome_checkout.CheckOut(build, location) | |
| 190 return True | |
| 191 except AssertionError: | |
| 192 return False | |
| 193 | |
| 194 def _FetchPrebuiltPyauto(self, url, location): | |
| 195 """Fetches the specified Chrome build. | |
| 196 | |
| 197 Args: | |
| 198 url: URL where the build is located. | |
| 199 location: Location where the build will be downloaded. | |
| 200 | |
| 201 Returns: | |
| 202 True if successful, otherwise False. | |
| 203 """ | |
| 204 fetch_build = FetchPrebuilt(url, location, self._platform) | |
| 205 if fetch_build.DoesUrlExist(url): | |
| 206 return fetch_build.Run() == 0 | |
| 207 else: | |
| 208 return False | |
| 209 | |
| 210 def _DownloadInstaller(self, url, location): | |
| 211 """Downloads the Chrome installer. | |
| 212 | |
| 213 Args: | |
| 214 url: URL where the installer is located. | |
| 215 location: Location where installer will be downloaded. | |
| 216 | |
| 217 Returns: | |
| 218 True if successful, otherwise False. | |
| 219 """ | |
| 220 try: | |
| 221 self._Download(self._installer_name, url, location) | |
| 222 return True | |
| 223 except (IOError, RuntimeError): | |
| 224 return False | |
| 225 | |
| 226 def _DownloadDeps(self, build): | |
| 227 """Downloads Chrome build, source files, and Chrome installer. | |
| 228 | |
| 229 Args: | |
| 230 build: Chrome release build number. | |
| 231 | |
| 232 Returns: | |
| 233 True if successful, otherwise False. | |
| 234 """ | |
| 235 url = '%s%s/%s' % (self._url, build, self._platform) | |
| 236 location = os.path.join('%s', '%s%s') % (tempfile.gettempdir(), | |
| 237 self._dir_prefix, build) | |
| 238 if os.path.isdir(location): | |
| 239 self._download_dirs.append(location) | |
| 240 self._installers.append(os.path.join(location, self._installer_name)) | |
| 241 return True | |
| 242 else: | |
| 243 tmpdir = tempfile.mkdtemp() | |
| 244 if self._CheckoutSourceFiles(build, tmpdir): | |
| 245 if self._FetchPrebuiltPyauto(url, tmpdir): | |
| 246 if self._DownloadInstaller(url, tmpdir): | |
| 247 try: | |
| 248 # This is a workaround because rename was causing problems. | |
| 249 shutil.copytree(tmpdir, location) | |
| 250 # Callback is there because hidden svn files are read-only, so | |
| 251 # we need to change their permissions on the fly to delete them. | |
| 252 shutil.rmtree(tmpdir, onerror=self._OnError) | |
| 253 self._download_dirs.append(location) | |
| 254 self._installers.append(os.path.join(location, | |
| 255 self._installer_name)) | |
| 256 return True | |
| 257 except(OSError, IOError), err: | |
| 258 return False | |
| 259 return False | |
| 260 | |
| 261 def _OnError(self, func, path, exc_info): | |
|
Nirnimesh
2012/08/09 21:32:48
I've seen this elsewhere. Move this and line 252,
nkang
2012/08/16 23:46:24
Put line no. 252 in a helper method, and made the
| |
| 262 """Callback for shutil.rmtree.""" | |
| 263 if not os.access(path, os.W_OK): | |
| 264 os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) | |
| 265 func(path) | |
| 266 | |
| 267 def _DeleteBuild(self): | |
| 268 """Uninstalls Chrome.""" | |
| 269 self._installation.Uninstall() | |
| 270 self._build_iterator = iter(self._builds) | |
| 271 self._dir_iterator = iter(self._download_dirs) | |
| 272 self._current_build = next(self._build_iterator, None) | |
| 273 self._current_location = next(self._dir_iterator, None) | |
| 274 self._RemovePaths() | |
| 275 | |
| 276 def _DeleteDepFiles(self): | |
| 277 """Deletes Chrome related files that were downloaded for testing.""" | |
| 278 for path in self._download_dirs: | |
| 279 try: | |
| 280 shutil.rmtree(path, onerror=self._OnError) | |
| 281 except shutil.Error, err: | |
| 282 logging.log(logging.ERROR, err) | |
| 283 return -1 | |
| 284 return 0 | |
| 285 | |
| 286 def _Download(self, dfile, url, dest=None): | |
|
Nirnimesh
2012/08/09 21:32:48
I've seen similar function elsewhere. Reuse code.
nkang
2012/08/16 23:46:24
The only other method that's somewhat similar is c
| |
| 287 """Downloads a file from the specified URL. | |
| 288 | |
| 289 Args: | |
| 290 dfile: Name of the file to download. | |
| 291 url: URL where the file is located. | |
| 292 dest: Location where file will be downloaded. Default is CWD. | |
| 293 | |
| 294 Returns: | |
| 295 Zero if successful, otherwise a non-zero value. | |
| 296 """ | |
| 297 filename = ((dest and os.path.exists(dest)) and os.path.join(dest, dfile) | |
| 298 or os.path.join(tempfile.gettempdir(), dfile)) | |
| 299 file_url = '%s/%s' % (url, dfile) | |
| 300 if not self._DoesUrlExist(file_url): | |
| 301 raise RuntimeError('Either the URL or the file name is invalid.') | |
| 302 try: | |
| 303 d = urllib.urlretrieve(file_url, filename) | |
| 304 except IOError, err: | |
| 305 raise err | |
| 306 return os.path.isfile(d[0]) | |
| 307 | |
| 308 def _DoesUrlExist(self, url): | |
| 309 """Checks if a URL exists. | |
| 310 | |
| 311 Args: | |
| 312 url: URL to be verified. | |
| 313 | |
| 314 Returns: | |
| 315 True if the URL exists, otherwise False. | |
| 316 """ | |
| 317 parse = urlparse.urlparse(url) | |
| 318 if parse[0] == '' or parse[1] == '': | |
| 319 return False | |
| 320 try: | |
| 321 connection = httplib.HTTPConnection(parse.netloc) | |
| 322 connection.request('HEAD', parse.path) | |
| 323 response = connection.getresponse() | |
| 324 except (socket.error, socket.gaierror): | |
| 325 return False | |
| 326 finally: | |
| 327 connection.close() | |
| 328 if response.status == 302 or response.status == 301: | |
| 329 return self._DoesUrlExist(response.getheader('location')) | |
| 330 return response.status == 200 | |
| 331 | |
| 332 | |
| 333 class Main(object): | |
| 334 """Main program for running Updater tests.""" | |
| 335 | |
| 336 _tests_file = 'PYAUTO_TESTS' | |
| 337 _mod_path = sys.argv[0] | |
| 338 _pyauto_doc_url = 'http://dev.chromium.org/developers/testing/pyauto' | |
| 339 | |
| 340 def __init__(self): | |
| 341 self._ParseArgs() | |
| 342 self._Run() | |
| 343 | |
| 344 def _GetUnitTests(self): | |
|
Nirnimesh
2012/08/09 21:32:48
remove 'Unit'
nkang
2012/08/16 23:46:24
Renamed to _GetTests.
| |
| 345 """Returns a list of unittests from the calling script.""" | |
| 346 mod_name = [os.path.splitext(os.path.basename(self._mod_path))[0]] | |
| 347 if os.path.dirname(self._mod_path) not in sys.path: | |
| 348 sys.path.append(os.path.dirname(self._mod_path)) | |
| 349 return unittest.defaultTestLoader.loadTestsFromNames(mod_name) | |
| 350 | |
| 351 def _Run(self): | |
| 352 """Runs the unit tests.""" | |
| 353 tests = self._GetUnitTests() | |
| 354 result = GTestTextTestRunner(verbosity=1).run(tests) | |
| 355 del(tests) | |
| 356 if not result.wasSuccessful(): | |
| 357 print >>sys.stderr, ('Tests can be disabled by editing %s. Ref: %s' | |
| 358 % (self._tests_file, self._pyauto_doc_url)) | |
| 359 sys.exit(1) | |
| 360 else: | |
|
Nirnimesh
2012/08/09 21:32:48
remove the else part. it's redundant
nkang
2012/08/16 23:46:24
Removed else.
| |
| 361 sys.exit(0) | |
| 362 | |
| 363 def _ParseArgs(self): | |
|
Nirnimesh
2012/08/09 21:32:48
Move _ParseArgs before _Run
nkang
2012/08/16 23:46:24
Done.
| |
| 364 """Parses command line arguments.""" | |
| 365 global _OPTIONS | |
| 366 parser = optparse.OptionParser() | |
| 367 parser.add_option( | |
| 368 '-b', '--builds', type='string', default='', dest='builds', | |
| 369 help='Specifies the two (or more) builds needed for testing.') | |
| 370 parser.add_option( | |
| 371 '-u', '--url', type='string', default='', dest='url', | |
| 372 help='Specifies the chrome-master2 url, without the build number.') | |
|
Nirnimesh
2012/08/09 21:32:48
do not refer internal URLs/hostname.
nkang
2012/08/16 23:46:24
Removed the part that referred to an internal URL.
| |
| 373 parser.add_option( | |
| 374 '-d', '--dir', type='string', default=os.getcwd(), | |
| 375 help='Specifies directory where the installer will be downloaded.') | |
| 376 parser.add_option( | |
| 377 '-o', '--options', type='string', default='', | |
| 378 help='Specifies any additional Chrome options (i.e. --system-level).') | |
| 379 opts, args = parser.parse_args() | |
| 380 _OPTIONS = opts | |
|
Nirnimesh
2012/08/09 21:32:48
verify the sanity of the args here, esp --builds
Nirnimesh
2012/08/09 21:32:48
Before firing off the install tests, it'd be nice
nkang
2012/08/16 23:46:24
Wrote a new method called ValidateArgs, which conf
nkang
2012/08/16 23:46:24
I am not entirely sure how we can confirm that the
Nirnimesh
2012/08/22 07:06:34
Yes, running a test from test_basic would be fine.
nkang
2012/08/24 22:45:26
After taking everything into consideration, I thin
| |
| OLD | NEW |