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. | |
| 5 | |
| 6 """Test fixture for tests involving installing/updating Chrome. | |
| 7 | |
| 8 Provides an interface to install or update chrome from within a testcase, and | |
| 9 allows users to run pyauto tests using the installed version. User and system | |
|
Nirnimesh
2012/08/22 07:06:35
pyauto -. PyAuto
nkang
2012/08/24 22:45:26
Done.
| |
| 10 level installations are supported, and either one can be used for running the | |
| 11 pyauto tests. Currently the only platform that's supported is Windows. | |
|
Nirnimesh
2012/08/22 07:06:35
ditto
nkang
2012/08/24 22:45:26
Done.
| |
| 12 | |
| 13 | |
| 14 Include the following in your updater test script to make it run standalone. | |
| 15 | |
| 16 from install_test import Main | |
| 17 | |
| 18 if __name__ == '__main__': | |
| 19 Main() | |
| 20 """ | |
| 21 | |
| 22 import httplib | |
| 23 import logging | |
| 24 import optparse | |
| 25 import os | |
| 26 import platform | |
| 27 import re | |
| 28 import shutil | |
| 29 import stat | |
| 30 import sys | |
| 31 import tempfile | |
| 32 import unittest | |
| 33 import urllib | |
| 34 import urlparse | |
| 35 | |
| 36 import chrome_checkout | |
| 37 import chrome_installer | |
| 38 from chrome_installer import ChromeInstallation | |
| 39 | |
| 40 sys.path.append(os.path.join(os.path.pardir, 'pyautolib')) | |
| 41 | |
| 42 # This import should go after sys.path is set appropriately. | |
| 43 from fetch_prebuilt_pyauto import FetchPrebuilt | |
| 44 import pyauto_utils | |
| 45 from pyauto_utils import GTestTextTestRunner | |
| 46 | |
| 47 _OPTIONS = None | |
| 48 | |
| 49 | |
| 50 class InstallTest(unittest.TestCase): | |
| 51 """Base updater test class. | |
| 52 | |
| 53 All dependencies, such as the specified Chrome builds, source files, and | |
| 54 installers are downloaded at the beginning of the test. Dependencies are | |
| 55 downloaded in the temp directory. This download only occurs once, before | |
| 56 the first test is executed. A PyUITest object is created whenever a user | |
| 57 installs or updates Chrome, using dependencies that correspond with that | |
| 58 particular build. Users can utilize that object to run updater tests. All | |
| 59 updater tests should derive from this class. | |
| 60 | |
| 61 Example: | |
| 62 | |
| 63 class ProtectorUpdater(InstallTest): | |
| 64 | |
| 65 def testNoChangeOnCleanProfile(self): | |
| 66 self.assertFalse(self._pyauto.GetProtectorState()['showing_change']) | |
| 67 self.UpdateBuild() | |
| 68 self.assertFalse(self._pyauto.GetProtectorState()['showing_change']) | |
|
Nirnimesh
2012/08/22 07:06:35
include the __name__ == '__main__' portion as well
nkang
2012/08/24 22:45:26
I had initially included the __name__=='__main__'
| |
| 69 """ | |
| 70 | |
| 71 _build_iterator = None | |
|
Nirnimesh
2012/08/22 07:06:35
This is a long list of member vars. At least some
nkang
2012/08/24 22:45:26
Yes they do, and now they have comments.
| |
| 72 _current_build = '' | |
| 73 _current_location = '' | |
| 74 _dir_prefix = '__CHRBLD__' | |
| 75 _dir_iterator = None | |
| 76 _installation = None | |
| 77 _installer_name = 'mini_installer.exe' | |
| 78 _pyauto = None | |
| 79 _installers = [] | |
| 80 _download_dirs = [] | |
| 81 _opts = None | |
| 82 | |
| 83 def __init__(self, methodName='runTest'): | |
| 84 unittest.TestCase.__init__(self, methodName) | |
| 85 self._platform = self._GetPlatform() | |
| 86 self._Initialize() | |
| 87 current_build = ChromeInstallation.GetCurrent() | |
| 88 if current_build: | |
| 89 current_build.Uninstall() | |
| 90 for build in self._builds: | |
| 91 if not self._DownloadDeps(build): | |
| 92 raise RuntimeError('Could not download dependencies.') | |
| 93 self._installer_iter = iter(self._installers) | |
| 94 self._dir_iterator = iter(self._download_dirs) | |
| 95 | |
| 96 def _Initialize(self): | |
| 97 """Sets test parameters.""" | |
| 98 global _OPTIONS | |
| 99 self._url = _OPTIONS.url | |
| 100 self._builds = _OPTIONS.builds and _OPTIONS.builds.split(',') or [] | |
| 101 if not self._url or not self._builds: | |
| 102 raise RuntimeError('Please specify a valid URL and two Chrome builds.') | |
| 103 self._builds.sort() | |
| 104 self._url = self._url.endswith('/') and self._url or self._url + '/' | |
| 105 self._options = (_OPTIONS.options and _OPTIONS.options.replace(',', ' ') | |
| 106 or '') | |
| 107 self._install_type = ('system-level' in self._options and | |
| 108 chrome_installer.InstallationType.SYSTEM or | |
| 109 chrome_installer.InstallationType.USER) | |
| 110 self._build_iterator = iter(self._builds) | |
| 111 self._current_build = next(self._build_iterator, None) | |
| 112 | |
| 113 def setUp(self): | |
| 114 """Called before each unittest to prepare the test fixture.""" | |
| 115 self.InstallBuild() | |
| 116 self.failIf(self._pyauto == None) | |
|
Nirnimesh
2012/08/22 07:06:35
use self.assertTrue(self._pyauto)
nkang
2012/08/24 22:45:26
Done.
| |
| 117 | |
| 118 def tearDown(self): | |
| 119 """Called at the end of each unittest to do any test related cleanup.""" | |
| 120 self._ClearModulesRegistry() | |
| 121 self._DeleteBuild() | |
| 122 | |
| 123 def _GetPlatform(self): | |
|
Nirnimesh
2012/08/22 07:06:35
@staticmethod
nkang
2012/08/24 22:45:26
Changed it to staticmethod and changed the name fr
| |
| 124 """Returns the platform name.""" | |
| 125 return ({'Windows': 'win', | |
| 126 'Darwin': 'mac', | |
| 127 'Linux': 'linux'}).get(platform.system()) | |
| 128 | |
| 129 def _ClearModulesRegistry(self): | |
|
Nirnimesh
2012/08/22 07:06:35
what do you mean by Registry?
Rename to _UnloadPyA
nkang
2012/08/24 22:45:26
I meant the modules registry (sys.modules), which
| |
| 130 """Deletes the PyUITest object and clears the modules registry.""" | |
|
Nirnimesh
2012/08/22 07:06:35
and update this line
nkang
2012/08/24 22:45:26
Done.
| |
| 131 if self._pyauto: | |
| 132 del self._pyauto | |
| 133 for module in ['pyauto', 'pyautolib', '_pyautolib']: | |
| 134 if module in sys.modules: | |
| 135 sys.modules.pop(module) | |
| 136 | |
| 137 def _RemovePaths(self): | |
|
Nirnimesh
2012/08/22 07:06:35
Remove -> Restore to match reality
nkang
2012/08/24 22:45:26
Done. 'To match reality', I like that ;)
| |
| 138 """Restores the sys.path variable to its original state.""" | |
| 139 sys.path = list(frozenset(sys.path)) | |
| 140 if self._current_location: | |
| 141 if self._current_location in sys.path: | |
| 142 sys.path.remove(self._current_location) | |
| 143 if os.path.join(self._current_location, 'pyautolib') in sys.path: | |
| 144 sys.path.remove(os.path.join(self._current_location, 'pyautolib')) | |
| 145 | |
| 146 def _Install(self): | |
| 147 """Helper method that installs Chrome and creates a PyUITest object.""" | |
| 148 self._pyauto = None | |
| 149 installer_path = next(self._installer_iter, None) | |
| 150 if not installer_path: | |
| 151 raise RuntimeError('No more builds left to install.') | |
| 152 self._installation = chrome_installer.Install(installer_path, | |
| 153 self._install_type, | |
| 154 self._current_build, | |
| 155 self._options) | |
| 156 try: | |
| 157 import pyauto | |
|
Nirnimesh
2012/08/22 07:06:35
only this line should be in the try block
nkang
2012/08/24 22:45:26
Done.
| |
| 158 self._pyauto = pyauto.PyUITest(methodName='runTest', | |
| 159 browser_path=os.path.dirname( | |
| 160 self._installation.GetExePath())) | |
| 161 self._pyauto.suite_holder = pyauto.PyUITestSuite([]) | |
| 162 self._pyauto.setUp() | |
| 163 except ImportError, err: | |
| 164 logging.log(logging.ERROR, err) | |
|
Nirnimesh
2012/08/22 07:06:35
logging.error()
nkang
2012/08/24 22:45:26
Changed here and elsewhere.
| |
| 165 self._pyauto = None | |
| 166 | |
| 167 def InstallBuild(self): | |
| 168 self._current_location = next(self._dir_iterator, None) | |
| 169 sys.path.insert(0, self._current_location) | |
|
Nirnimesh
2012/08/22 07:06:35
Why does it need to be in the front?
nkang
2012/08/24 22:45:26
Just an extra precaution to make sure the correct
| |
| 170 sys.path.insert(1, os.path.join(self._current_location, 'pyautolib')) | |
| 171 self._Install() | |
| 172 | |
| 173 def _Update(self): | |
| 174 """Helper method for updating Chrome.""" | |
| 175 self._RemovePaths() | |
| 176 self._current_location = next(self._dir_iterator, None) | |
| 177 assert (self._current_location) | |
| 178 sys.path.insert(0, self._current_location) | |
| 179 sys.path.insert(1, os.path.join(self._current_location, 'pyautolib')) | |
| 180 build = next(self._build_iterator, None) | |
| 181 if not build: | |
| 182 raise RuntimeError('No more builds left to install. Following builds ' | |
| 183 'have already been installed: %r' % self._builds) | |
| 184 self._current_build = build | |
| 185 self._Install() | |
| 186 | |
| 187 def UpdateBuild(self): | |
| 188 """Installs the second Chrome build specified in the command args.""" | |
|
Nirnimesh
2012/08/22 07:06:35
command -> command line
nkang
2012/08/24 22:45:26
Done.
| |
| 189 if self._pyauto: | |
| 190 self._pyauto.TearDown() | |
| 191 self._ClearModulesRegistry() | |
| 192 self._Update() | |
| 193 | |
| 194 def _SrcFilesExist(self, root, items): | |
| 195 """Checks if specified files/folders exist at specified 'root' folder. | |
|
Nirnimesh
2012/08/22 07:06:35
'root' folder -> location
nkang
2012/08/24 22:45:26
Done.
| |
| 196 | |
| 197 Args: | |
| 198 root: Parent folder where all the source directories reside. | |
| 199 items: List of files/folders to be verified for existence in the root. | |
| 200 | |
| 201 Returns: | |
| 202 True, if all sub-folders exist in the root, otherwise False. | |
| 203 """ | |
| 204 return all(map(lambda p: os.path.exists(p) and True or False, | |
|
Nirnimesh
2012/08/22 07:06:35
os.path.exists(p) already returns a boolean. Rewri
nkang
2012/08/24 22:45:26
Done.
| |
| 205 [os.path.join(root, path) for path in items])) | |
| 206 | |
| 207 def _CheckoutSourceFiles(self, build, location): | |
| 208 """Checks out source files associated with the current build. | |
| 209 | |
| 210 Args: | |
| 211 build: Chrome release version number. | |
| 212 location: Destination where source files will be saved. | |
| 213 Returns: | |
| 214 Zero if successful, otherwise a negative value. | |
| 215 """ | |
| 216 try: | |
| 217 chrome_checkout.CheckOut(build, location) | |
| 218 return True | |
| 219 except AssertionError: | |
| 220 return False | |
| 221 | |
| 222 def _FetchPrebuiltPyauto(self, url, location): | |
| 223 """Fetches the specified Chrome build. | |
| 224 | |
| 225 Args: | |
| 226 url: URL where the build is located. | |
| 227 location: Location where the build will be downloaded. | |
| 228 | |
| 229 Returns: | |
| 230 True if successful, otherwise False. | |
| 231 """ | |
| 232 fetch_build = FetchPrebuilt(url, location, self._platform) | |
| 233 if pyauto_utils.DoesUrlExist(url): | |
| 234 return fetch_build.Run() == 0 | |
| 235 else: | |
|
Nirnimesh
2012/08/22 07:06:35
Remove else:
and left-indent the next line.
nkang
2012/08/24 22:45:26
Done and done.
| |
| 236 return False | |
| 237 | |
| 238 def _DownloadInstaller(self, url, location): | |
| 239 """Downloads the Chrome installer. | |
| 240 | |
| 241 Args: | |
| 242 url: URL where the installer is located. | |
| 243 location: Location where installer will be downloaded. | |
| 244 | |
| 245 Returns: | |
| 246 True if successful, otherwise False. | |
| 247 """ | |
| 248 try: | |
| 249 self._Download(self._installer_name, url, location) | |
| 250 return True | |
| 251 except (IOError, RuntimeError): | |
| 252 return False | |
| 253 | |
| 254 def _DownloadDeps(self, build): | |
| 255 """Downloads Chrome build, source files, and Chrome installer. | |
| 256 | |
| 257 Args: | |
| 258 build: Chrome release build number. | |
| 259 | |
| 260 Returns: | |
| 261 True if successful, otherwise False. | |
| 262 """ | |
| 263 url = '%s%s/%s' % (self._url, build, self._platform) | |
| 264 location = os.path.join('%s', '%s%s') % (tempfile.gettempdir(), | |
|
Nirnimesh
2012/08/22 07:06:35
Either use string formattting to join (like previo
nkang
2012/08/24 22:45:26
I mixed it because the last two strings (self._dir
| |
| 265 self._dir_prefix, build) | |
| 266 if os.path.isdir(location): | |
| 267 self._download_dirs.append(location) | |
| 268 self._installers.append(os.path.join(location, self._installer_name)) | |
| 269 return True | |
| 270 else: | |
| 271 tmpdir = tempfile.mkdtemp() | |
| 272 if self._CheckoutSourceFiles(build, tmpdir): | |
| 273 if self._FetchPrebuiltPyauto(url, tmpdir): | |
| 274 if self._DownloadInstaller(url, tmpdir): | |
| 275 try: | |
| 276 # This is a workaround because rename was causing problems. | |
| 277 shutil.copytree(tmpdir, location) | |
| 278 # Callback is there because hidden svn files are read-only, so | |
| 279 # we need to change their permissions on the fly to delete them. | |
| 280 self._DeleteDir(tmpdir) | |
| 281 self._download_dirs.append(location) | |
| 282 self._installers.append(os.path.join(location, | |
| 283 self._installer_name)) | |
| 284 return True | |
| 285 except(OSError, IOError), err: | |
| 286 return False | |
| 287 return False | |
| 288 | |
| 289 def _DeleteBuild(self): | |
| 290 """Uninstalls Chrome.""" | |
| 291 self._installation.Uninstall() | |
| 292 self._build_iterator = iter(self._builds) | |
| 293 self._dir_iterator = iter(self._download_dirs) | |
| 294 self._current_build = next(self._build_iterator, None) | |
| 295 self._current_location = next(self._dir_iterator, None) | |
| 296 self._RemovePaths() | |
| 297 | |
| 298 def _DeleteDir(self, dir_name): | |
| 299 """Deletes a directory. | |
| 300 | |
| 301 Args: | |
| 302 dir_name: Name of the directory to delete. | |
| 303 """ | |
| 304 def _OnError(func, path, exc_info): | |
| 305 """Callback for shutil.rmtree.""" | |
| 306 if not os.access(path, os.W_OK): | |
| 307 os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) | |
| 308 func(path) | |
| 309 | |
| 310 if os.path.isdir(dir_name): | |
| 311 shutil.rmtree(dir_name, onerror=_OnError) | |
| 312 | |
| 313 def _Download(self, dfile, url, dest=None): | |
| 314 """Downloads a file from the specified URL. | |
| 315 | |
| 316 Args: | |
| 317 dfile: Name of the file to download. | |
| 318 url: URL where the file is located. | |
| 319 dest: Location where file will be downloaded. Default is CWD. | |
| 320 | |
| 321 Returns: | |
| 322 Zero if successful, otherwise a non-zero value. | |
| 323 """ | |
| 324 filename = ((dest and os.path.exists(dest)) and os.path.join(dest, dfile) | |
| 325 or os.path.join(tempfile.gettempdir(), dfile)) | |
| 326 file_url = '%s/%s' % (url, dfile) | |
| 327 if not pyauto_utils.DoesUrlExist(file_url): | |
| 328 raise RuntimeError('Either the URL or the file name is invalid.') | |
| 329 try: | |
| 330 d = urllib.urlretrieve(file_url, filename) | |
|
Nirnimesh
2012/08/22 07:06:35
Don't use one-letter varnames.
nkang
2012/08/24 22:45:26
Changed var name to dfile (as in downloaded file),
| |
| 331 except IOError, err: | |
| 332 raise err | |
| 333 return os.path.isfile(d[0]) | |
| 334 | |
| 335 | |
| 336 class Main(object): | |
| 337 """Main program for running Updater tests.""" | |
| 338 | |
| 339 _tests_file = 'PYAUTO_TESTS' | |
| 340 _mod_path = sys.argv[0] | |
| 341 _pyauto_doc_url = 'http://dev.chromium.org/developers/testing/pyauto' | |
| 342 | |
| 343 def __init__(self): | |
| 344 self._ParseArgs() | |
| 345 self._Run() | |
| 346 | |
| 347 def _ParseArgs(self): | |
| 348 """Parses command line arguments.""" | |
| 349 global _OPTIONS | |
| 350 parser = optparse.OptionParser() | |
| 351 parser.add_option( | |
| 352 '-b', '--builds', type='string', default='', dest='builds', | |
| 353 help='Specifies the two (or more) builds needed for testing.') | |
| 354 parser.add_option( | |
| 355 '-u', '--url', type='string', default='', dest='url', | |
| 356 help='Specifies the build url, without the build number.') | |
| 357 parser.add_option( | |
| 358 '-o', '--options', type='string', default='', | |
| 359 help='Specifies any additional Chrome options (i.e. --system-level).') | |
| 360 opts, args = parser.parse_args() | |
| 361 _OPTIONS = opts | |
| 362 self.ValidateArgs(opts) | |
| 363 | |
| 364 def ValidateArgs(self, opts): | |
| 365 """Verifies the sanity of the command arguments. | |
| 366 | |
| 367 Confirms that all specified builds have a valid version number, and the | |
| 368 build urls are valid. | |
| 369 | |
| 370 Args: | |
| 371 opts: An object containing values for all command args. | |
| 372 """ | |
| 373 builds = opts.builds.split(',') | |
| 374 for build in builds: | |
| 375 if not re.match('\d+\.\d+\.\d+\.\d+', build): | |
| 376 raise RuntimeError('Invalid build number: %s' % build) | |
| 377 if not pyauto_utils.DoesUrlExist('%s/%s/' % (opts.url, build)): | |
| 378 raise RuntimeError('Could not locate build no. %s' % build) | |
| 379 | |
| 380 def _GetTests(self): | |
| 381 """Returns a list of unittests from the calling script.""" | |
| 382 mod_name = [os.path.splitext(os.path.basename(self._mod_path))[0]] | |
| 383 if os.path.dirname(self._mod_path) not in sys.path: | |
| 384 sys.path.append(os.path.dirname(self._mod_path)) | |
| 385 return unittest.defaultTestLoader.loadTestsFromNames(mod_name) | |
| 386 | |
| 387 def _Run(self): | |
| 388 """Runs the unit tests.""" | |
| 389 tests = self._GetTests() | |
| 390 result = GTestTextTestRunner(verbosity=1).run(tests) | |
| 391 del(tests) | |
| 392 if not result.wasSuccessful(): | |
| 393 print >>sys.stderr, ('Tests can be disabled by editing %s. Ref: %s' | |
|
Nirnimesh
2012/08/22 07:06:35
Really? I don't see any PYAUTO_TESTS file in this
nkang
2012/08/24 22:45:26
We haven't made any changes to PYAUTO_TESTS. That'
| |
| 394 % (self._tests_file, self._pyauto_doc_url)) | |
| 395 sys.exit(1) | |
| 396 sys.exit(0) | |
| OLD | NEW |