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 tests using installed version of Chrome. User and system | |
| 10 level installations are supported, and either one can be used for running the | |
| 11 tests. Currently the only platform it supports is Windows. | |
| 12 """ | |
| 13 | |
| 14 import logging | |
| 15 import optparse | |
| 16 import os | |
| 17 import platform | |
| 18 import re | |
| 19 import shutil | |
| 20 import stat | |
| 21 import sys | |
| 22 import tempfile | |
| 23 import unittest | |
| 24 import urllib | |
| 25 | |
| 26 import chrome_installer_win | |
| 27 | |
| 28 sys.path.append(os.path.join(os.path.pardir, 'pyautolib')) | |
|
kkania
2012/10/02 17:05:16
this assumes the cwd is install_test, right? You s
nkang
2012/10/03 22:12:01
Done.
| |
| 29 | |
| 30 # This import should go after sys.path is set appropriately. | |
| 31 import pyauto_utils | |
| 32 import selenium.webdriver.chrome.service as service | |
|
kkania
2012/10/02 17:05:16
how is this found?
nkang
2012/10/03 22:12:01
Appended path to sys.path variable.
| |
| 33 from selenium import webdriver | |
| 34 | |
| 35 | |
| 36 class InstallTest(unittest.TestCase): | |
| 37 """Base updater test class. | |
| 38 | |
| 39 All dependencies, like Chrome installers and ChromeDriver, are downloaded at | |
| 40 the beginning of the test. Dependencies are downloaded in the temp directory. | |
| 41 This download occurs only once, before the first test is executed. Each test | |
| 42 case starts an instance of ChromeDriver and terminates it upon completion. | |
| 43 All updater tests should derive from this class. | |
| 44 | |
| 45 Example: | |
| 46 | |
| 47 class ProtectorUpdater(InstallTest): | |
| 48 | |
| 49 def testCanOpenGoogle(self): | |
| 50 self._driver.get('http://www.google.com/') | |
| 51 self.UpdateBuild() | |
| 52 self._driver.get('http://www.msn.com/') | |
| 53 | |
| 54 Include the following in your updater test script to make it run standalone. | |
| 55 | |
| 56 from install_test import Main | |
| 57 | |
| 58 if __name__ == '__main__': | |
| 59 Main() | |
| 60 | |
| 61 To fire off an updater test, use the command below. | |
| 62 python test_script.py --url=<URL> --builds=22.0.1230.0,22.0.1231.0 | |
| 63 """ | |
| 64 | |
| 65 _dir_prefix = '__CHRBLD__' | |
|
kkania
2012/10/02 17:05:16
http://google-styleguide.googlecode.com/svn/trunk/
nkang
2012/10/03 22:12:01
Changed both constant names, as per the style guid
| |
| 66 _installer = 'mini_installer.exe' | |
| 67 _download_dirs = [] | |
| 68 _opts = None | |
| 69 _chrome_driver = '' | |
| 70 | |
| 71 def __init__(self, methodName='runTest'): | |
| 72 unittest.TestCase.__init__(self, methodName) | |
| 73 self._platform = InstallTest.GetPlatform() | |
| 74 self._Initialize() | |
| 75 current_build = chrome_installer_win.ChromeInstallation.GetCurrent() | |
| 76 if current_build: | |
| 77 current_build.Uninstall() | |
| 78 for build in self._builds: | |
| 79 if not self._DownloadDeps(build): | |
|
kkania
2012/10/02 17:05:16
make this throw instead
nkang
2012/10/03 22:12:01
The download now occurs in the static 'InitTestFix
| |
| 80 raise RuntimeError('Could not download dependencies.') | |
| 81 self._dir_iterator = iter(self._download_dirs) | |
| 82 | |
| 83 def _Initialize(self): | |
| 84 """Sets test parameters.""" | |
| 85 self._url = self._opts.url | |
| 86 self._builds = self._opts.builds and self._opts.builds.split(',') or [] | |
| 87 if not self._url or not self._builds: | |
| 88 raise RuntimeError('Please specify a valid URL and two Chrome builds.') | |
| 89 self._builds.sort() | |
|
kkania
2012/10/02 17:05:16
don't sort
nkang
2012/10/03 22:12:01
Got rid of it.
| |
| 90 self._url = self._url.endswith('/') and self._url or self._url + '/' | |
| 91 self._options = self._opts.options.split(',') if self._opts.options else [] | |
| 92 self._install_type = ('system-level' in self._options and | |
| 93 chrome_installer_win.InstallationType.SYSTEM or | |
| 94 chrome_installer_win.InstallationType.USER) | |
| 95 self._build_iterator = iter(self._builds) | |
| 96 self._current_build = next(self._build_iterator, None) | |
| 97 | |
| 98 def setUp(self): | |
| 99 """Called before each unittest to prepare the test fixture.""" | |
| 100 self.InstallBuild() | |
| 101 dir_name = self._dir_prefix + max(self._builds) | |
|
kkania
2012/10/02 17:05:16
using max is not correct for string version compar
nkang
2012/10/03 22:12:01
This statement has been removed.
| |
| 102 self._StartChromeDriver(self._chrome_driver, | |
| 103 self._installation.GetExePath()) | |
| 104 | |
| 105 def tearDown(self): | |
| 106 """Called at the end of each unittest to do any test related cleanup.""" | |
| 107 self._ShutDownChromeDriver() | |
| 108 self._DeleteBuild() | |
| 109 | |
| 110 def _StartChromeDriver(self, chrome_driver_location, chrome_location): | |
| 111 """Starts ChromeDriver. | |
| 112 | |
| 113 Args: | |
| 114 chrome_driver_locations: Location of ChromeDriver executable. | |
| 115 chrome_location: Location of the Chrome binary. | |
| 116 """ | |
| 117 self._service = service.Service(chrome_driver_location) | |
| 118 self._service.start() | |
| 119 self._capabilities = {'chrome.binary' : chrome_location} | |
| 120 self._driver = webdriver.Remote(self._service.service_url, | |
| 121 self._capabilities) | |
| 122 | |
| 123 def _ShutDownChromeDriver(self): | |
| 124 """Shuts down ChromeDriver.""" | |
| 125 self._driver.quit() | |
| 126 self._service.stop() | |
| 127 | |
| 128 @staticmethod | |
| 129 def GetPlatform(): | |
| 130 """Returns the platform name.""" | |
| 131 return ({'Windows': 'win', | |
| 132 'Darwin': 'mac', | |
| 133 'Linux': 'linux'}).get(platform.system()) | |
| 134 | |
| 135 def _Install(self): | |
| 136 """Helper method that installs Chrome.""" | |
| 137 installer_path = os.path.join(self._current_location, self._installer) | |
| 138 if not installer_path: | |
|
kkania
2012/10/02 17:05:16
drop this check
nkang
2012/10/03 22:12:01
Dropped it like it was hot!
| |
| 139 raise RuntimeError('No more builds left to install.') | |
| 140 self._installation = chrome_installer_win.Install(installer_path, | |
| 141 self._install_type, | |
| 142 self._current_build, | |
| 143 self._options) | |
| 144 | |
| 145 def InstallBuild(self): | |
| 146 """Installs a Chrome build.""" | |
| 147 self._current_location = next(self._dir_iterator, None) | |
| 148 self._Install() | |
| 149 | |
| 150 def UpdateBuild(self): | |
| 151 """Updates Chrome by installing a newer version.""" | |
| 152 self._driver.quit() | |
| 153 self._current_location = next(self._dir_iterator, None) | |
| 154 assert(self._current_location) | |
| 155 build = next(self._build_iterator, None) | |
| 156 if not build: | |
| 157 raise RuntimeError('No more builds left to install. Following builds ' | |
| 158 'have already been installed: %r' % self._builds) | |
| 159 self._current_build = build | |
| 160 self._Install() | |
| 161 self._driver = webdriver.Remote(self._service.service_url, | |
| 162 self._capabilities) | |
| 163 | |
| 164 def _SrcFilesExist(self, root, items): | |
|
kkania
2012/10/02 17:05:16
remove
nkang
2012/10/03 22:12:01
Removed. This method was used when the framework w
| |
| 165 """Checks if specified files/folders exist at the specified location. | |
| 166 | |
| 167 Args: | |
| 168 root: Parent folder where all the source directories reside. | |
| 169 items: List of files/folders to be verified for existence in the root. | |
| 170 | |
| 171 Returns: | |
| 172 True, if all sub-folders exist in the root, otherwise False. | |
| 173 """ | |
| 174 return all([os.path.exists(os.path.join(root, x)) for x in items]) | |
| 175 | |
| 176 def _DownloadChromeDriver(self): | |
| 177 """Downloads ChromeDriver executable. | |
| 178 | |
| 179 Returns: | |
| 180 True, if download is successful, otherwise False. | |
|
kkania
2012/10/02 17:05:16
raise instead of returning boolean
nkang
2012/10/03 22:12:01
This method no longer exists, so this statement is
| |
| 181 """ | |
| 182 build = max(self._builds) | |
| 183 url = '%s%s/%s/%s/' % (self._url, build, self._platform, | |
| 184 'chrome-win32.test') | |
| 185 dir_name = os.path.join(tempfile.gettempdir(), 'CHROMEDRIVER-' + build) | |
| 186 if os.path.isdir(dir_name): | |
| 187 self._chrome_driver = os.path.join(dir_name, 'chromedriver.exe') | |
| 188 return True | |
| 189 else: | |
| 190 tmpdir = tempfile.mkdtemp() | |
| 191 if self._Download('chromedriver.exe', url, tmpdir): | |
| 192 shutil.move(tmpdir, dir_name) | |
| 193 self._chrome_driver = os.path.join(dir_name, 'chromedriver.exe') | |
| 194 return True | |
| 195 return False | |
| 196 | |
| 197 def _DownloadDeps(self, build): | |
| 198 """Downloads Chrome installer and ChromeDriver. | |
| 199 | |
| 200 Args: | |
| 201 build: Chrome release build number. | |
| 202 | |
| 203 Returns: | |
| 204 True if successful, otherwise False. | |
|
kkania
2012/10/02 17:05:16
raise instead of returning false
nkang
2012/10/03 22:12:01
This method was removed, so this is no longer rele
| |
| 205 """ | |
| 206 url = '%s%s/%s' % (self._url, build, self._platform) | |
| 207 dir_name = '%s%s' % (self._dir_prefix, build) | |
| 208 location = os.path.join(tempfile.gettempdir(), dir_name) | |
| 209 if not self._DownloadChromeDriver(): | |
| 210 return False | |
| 211 if os.path.isdir(location): | |
| 212 self._download_dirs.append(location) | |
| 213 return True | |
| 214 else: | |
| 215 tmpdir = tempfile.mkdtemp() | |
| 216 if self._Download(self._installer, url, tmpdir): | |
| 217 shutil.move(tmpdir, location) | |
| 218 self._download_dirs.append(location) | |
| 219 return True | |
| 220 return False | |
| 221 | |
| 222 def _DeleteBuild(self): | |
| 223 """Uninstalls Chrome.""" | |
| 224 self._installation.Uninstall() | |
| 225 | |
| 226 def _DeleteDir(self, dir_name): | |
| 227 """Deletes a directory. | |
| 228 | |
| 229 Args: | |
| 230 dir_name: Name of the directory to delete. | |
| 231 """ | |
| 232 def _OnError(func, path, exc_info): | |
| 233 """Callback for shutil.rmtree.""" | |
| 234 if not os.access(path, os.W_OK): | |
| 235 os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) | |
| 236 func(path) | |
| 237 | |
| 238 if os.path.isdir(dir_name): | |
| 239 shutil.rmtree(dir_name, onerror=_OnError) | |
| 240 | |
| 241 def _Download(self, dfile, url, dest=None): | |
| 242 """Downloads a file from the specified URL. | |
| 243 | |
| 244 Args: | |
| 245 dfile: Name of the file to download. | |
| 246 url: URL where the file is located. | |
| 247 dest: Location where file will be downloaded. Default is CWD. | |
| 248 | |
| 249 Returns: | |
| 250 Zero if successful, otherwise a non-zero value. | |
| 251 """ | |
| 252 filename = ((dest and os.path.exists(dest)) and os.path.join(dest, dfile) | |
| 253 or os.path.join(tempfile.gettempdir(), dfile)) | |
| 254 file_url = '%s/%s' % (url, dfile) | |
| 255 if not pyauto_utils.DoesUrlExist(file_url): | |
| 256 raise RuntimeError('Either the URL or the file name is invalid.') | |
| 257 try: | |
| 258 dfile = urllib.urlretrieve(file_url, filename) | |
| 259 except IOError, err: | |
|
kkania
2012/10/02 17:05:16
no point to catching this if you're just going to
nkang
2012/10/03 22:12:01
Removed.
| |
| 260 raise err | |
| 261 return os.path.isfile(dfile[0]) | |
|
kkania
2012/10/02 17:05:16
instead of returning true/false, raise exception i
nkang
2012/10/03 22:12:01
Done and updated the docstring.
| |
| 262 | |
| 263 @staticmethod | |
| 264 def SetOptions(opts): | |
| 265 """Static method for passing command options to InstallTest. | |
| 266 | |
| 267 We do not instantiate InstallTest. Therefore, command arguments cannot | |
| 268 be passed to its constructor. Since InstallTest needs to use these options | |
| 269 and using globals is not an option, this method can be used by the Main | |
| 270 class to pass the arguments it parses onto InstallTest. | |
| 271 """ | |
| 272 InstallTest._opts = opts | |
| 273 | |
| 274 | |
| 275 class Main(object): | |
| 276 """Main program for running Updater tests.""" | |
| 277 | |
| 278 _mod_path = sys.argv[0] | |
| 279 | |
| 280 def __init__(self): | |
| 281 self._ParseArgs() | |
| 282 self._Run() | |
| 283 | |
| 284 def _ParseArgs(self): | |
| 285 """Parses command line arguments.""" | |
| 286 parser = optparse.OptionParser() | |
| 287 parser.add_option( | |
| 288 '-b', '--builds', type='string', default='', dest='builds', | |
| 289 help='Specifies the two (or more) builds needed for testing.') | |
|
kkania
2012/10/02 17:05:16
drop the (or more). right now focus on two builds
nkang
2012/10/03 22:12:01
I dropped it for the time being, but Anantha has m
| |
| 290 parser.add_option( | |
| 291 '-u', '--url', type='string', default='', dest='url', | |
| 292 help='Specifies the build url, without the build number.') | |
| 293 parser.add_option( | |
| 294 '-o', '--options', type='string', default='', | |
| 295 help='Specifies any additional Chrome options (i.e. --system-level).') | |
| 296 opts, args = parser.parse_args() | |
| 297 self._SetLoggingConfiguration() | |
|
kkania
2012/10/02 17:05:16
this doesn't belong in _ParseArgs
nkang
2012/10/03 22:12:01
Moved this to the __init__ method.
| |
| 298 self._ValidateArgs(opts) | |
| 299 InstallTest.SetOptions(opts) | |
| 300 | |
| 301 def _ValidateArgs(self, opts): | |
| 302 """Verifies the sanity of the command arguments. | |
| 303 | |
| 304 Confirms that all specified builds have a valid version number, and the | |
| 305 build urls are valid. | |
| 306 | |
| 307 Args: | |
| 308 opts: An object containing values for all command args. | |
| 309 """ | |
| 310 builds = opts.builds.split(',') | |
| 311 for build in builds: | |
| 312 if not re.match('\d+\.\d+\.\d+\.\d+', build): | |
| 313 raise RuntimeError('Invalid build number: %s' % build) | |
| 314 if not pyauto_utils.DoesUrlExist('%s/%s/' % (opts.url, build)): | |
| 315 raise RuntimeError('Could not locate build no. %s' % build) | |
| 316 | |
| 317 def _SetLoggingConfiguration(self): | |
| 318 """Sets the basic logging configuration.""" | |
| 319 log_format = '%(asctime)s %(levelname)-8s %(message)s' | |
| 320 logging.basicConfig(level=logging.INFO, format=log_format) | |
| 321 | |
| 322 def _GetTests(self): | |
| 323 """Returns a list of unittests from the calling script.""" | |
| 324 mod_name = [os.path.splitext(os.path.basename(self._mod_path))[0]] | |
|
kkania
2012/10/02 17:05:16
just use sys.argv[0] directly and get rid of _mod_
nkang
2012/10/03 22:12:01
Got rid of it.
| |
| 325 if os.path.dirname(self._mod_path) not in sys.path: | |
| 326 sys.path.append(os.path.dirname(self._mod_path)) | |
| 327 return unittest.defaultTestLoader.loadTestsFromNames(mod_name) | |
| 328 | |
| 329 def _Run(self): | |
| 330 """Runs the unit tests.""" | |
| 331 tests = self._GetTests() | |
| 332 result = pyauto_utils.GTestTextTestRunner(verbosity=1).run(tests) | |
| 333 del(tests) | |
|
kkania
2012/10/02 17:05:16
?
nkang
2012/10/03 22:12:01
Done.
| |
| 334 if not result.wasSuccessful(): | |
| 335 print >>sys.stderr, ('Not all tests were successful.') | |
| 336 sys.exit(1) | |
| 337 sys.exit(0) | |
| OLD | NEW |