Chromium Code Reviews| Index: install_test/install_test.py |
| =================================================================== |
| --- install_test/install_test.py (revision 0) |
| +++ install_test/install_test.py (revision 0) |
| @@ -0,0 +1,337 @@ |
| +#!/usr/bin/env python |
| +# Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| + |
| +"""Test fixture for tests involving installing/updating Chrome. |
| + |
| +Provides an interface to install or update chrome from within a testcase, and |
| +allows users to run tests using installed version of Chrome. User and system |
| +level installations are supported, and either one can be used for running the |
| +tests. Currently the only platform it supports is Windows. |
| +""" |
| + |
| +import logging |
| +import optparse |
| +import os |
| +import platform |
| +import re |
| +import shutil |
| +import stat |
| +import sys |
| +import tempfile |
| +import unittest |
| +import urllib |
| + |
| +import chrome_installer_win |
| + |
| +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.
|
| + |
| +# This import should go after sys.path is set appropriately. |
| +import pyauto_utils |
| +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.
|
| +from selenium import webdriver |
| + |
| + |
| +class InstallTest(unittest.TestCase): |
| + """Base updater test class. |
| + |
| + All dependencies, like Chrome installers and ChromeDriver, are downloaded at |
| + the beginning of the test. Dependencies are downloaded in the temp directory. |
| + This download occurs only once, before the first test is executed. Each test |
| + case starts an instance of ChromeDriver and terminates it upon completion. |
| + All updater tests should derive from this class. |
| + |
| + Example: |
| + |
| + class ProtectorUpdater(InstallTest): |
| + |
| + def testCanOpenGoogle(self): |
| + self._driver.get('http://www.google.com/') |
| + self.UpdateBuild() |
| + self._driver.get('http://www.msn.com/') |
| + |
| + Include the following in your updater test script to make it run standalone. |
| + |
| + from install_test import Main |
| + |
| + if __name__ == '__main__': |
| + Main() |
| + |
| + To fire off an updater test, use the command below. |
| + python test_script.py --url=<URL> --builds=22.0.1230.0,22.0.1231.0 |
| + """ |
| + |
| + _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
|
| + _installer = 'mini_installer.exe' |
| + _download_dirs = [] |
| + _opts = None |
| + _chrome_driver = '' |
| + |
| + def __init__(self, methodName='runTest'): |
| + unittest.TestCase.__init__(self, methodName) |
| + self._platform = InstallTest.GetPlatform() |
| + self._Initialize() |
| + current_build = chrome_installer_win.ChromeInstallation.GetCurrent() |
| + if current_build: |
| + current_build.Uninstall() |
| + for build in self._builds: |
| + 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
|
| + raise RuntimeError('Could not download dependencies.') |
| + self._dir_iterator = iter(self._download_dirs) |
| + |
| + def _Initialize(self): |
| + """Sets test parameters.""" |
| + self._url = self._opts.url |
| + self._builds = self._opts.builds and self._opts.builds.split(',') or [] |
| + if not self._url or not self._builds: |
| + raise RuntimeError('Please specify a valid URL and two Chrome builds.') |
| + self._builds.sort() |
|
kkania
2012/10/02 17:05:16
don't sort
nkang
2012/10/03 22:12:01
Got rid of it.
|
| + self._url = self._url.endswith('/') and self._url or self._url + '/' |
| + self._options = self._opts.options.split(',') if self._opts.options else [] |
| + self._install_type = ('system-level' in self._options and |
| + chrome_installer_win.InstallationType.SYSTEM or |
| + chrome_installer_win.InstallationType.USER) |
| + self._build_iterator = iter(self._builds) |
| + self._current_build = next(self._build_iterator, None) |
| + |
| + def setUp(self): |
| + """Called before each unittest to prepare the test fixture.""" |
| + self.InstallBuild() |
| + 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.
|
| + self._StartChromeDriver(self._chrome_driver, |
| + self._installation.GetExePath()) |
| + |
| + def tearDown(self): |
| + """Called at the end of each unittest to do any test related cleanup.""" |
| + self._ShutDownChromeDriver() |
| + self._DeleteBuild() |
| + |
| + def _StartChromeDriver(self, chrome_driver_location, chrome_location): |
| + """Starts ChromeDriver. |
| + |
| + Args: |
| + chrome_driver_locations: Location of ChromeDriver executable. |
| + chrome_location: Location of the Chrome binary. |
| + """ |
| + self._service = service.Service(chrome_driver_location) |
| + self._service.start() |
| + self._capabilities = {'chrome.binary' : chrome_location} |
| + self._driver = webdriver.Remote(self._service.service_url, |
| + self._capabilities) |
| + |
| + def _ShutDownChromeDriver(self): |
| + """Shuts down ChromeDriver.""" |
| + self._driver.quit() |
| + self._service.stop() |
| + |
| + @staticmethod |
| + def GetPlatform(): |
| + """Returns the platform name.""" |
| + return ({'Windows': 'win', |
| + 'Darwin': 'mac', |
| + 'Linux': 'linux'}).get(platform.system()) |
| + |
| + def _Install(self): |
| + """Helper method that installs Chrome.""" |
| + installer_path = os.path.join(self._current_location, self._installer) |
| + 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!
|
| + raise RuntimeError('No more builds left to install.') |
| + self._installation = chrome_installer_win.Install(installer_path, |
| + self._install_type, |
| + self._current_build, |
| + self._options) |
| + |
| + def InstallBuild(self): |
| + """Installs a Chrome build.""" |
| + self._current_location = next(self._dir_iterator, None) |
| + self._Install() |
| + |
| + def UpdateBuild(self): |
| + """Updates Chrome by installing a newer version.""" |
| + self._driver.quit() |
| + self._current_location = next(self._dir_iterator, None) |
| + assert(self._current_location) |
| + build = next(self._build_iterator, None) |
| + if not build: |
| + raise RuntimeError('No more builds left to install. Following builds ' |
| + 'have already been installed: %r' % self._builds) |
| + self._current_build = build |
| + self._Install() |
| + self._driver = webdriver.Remote(self._service.service_url, |
| + self._capabilities) |
| + |
| + 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
|
| + """Checks if specified files/folders exist at the specified location. |
| + |
| + Args: |
| + root: Parent folder where all the source directories reside. |
| + items: List of files/folders to be verified for existence in the root. |
| + |
| + Returns: |
| + True, if all sub-folders exist in the root, otherwise False. |
| + """ |
| + return all([os.path.exists(os.path.join(root, x)) for x in items]) |
| + |
| + def _DownloadChromeDriver(self): |
| + """Downloads ChromeDriver executable. |
| + |
| + Returns: |
| + 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
|
| + """ |
| + build = max(self._builds) |
| + url = '%s%s/%s/%s/' % (self._url, build, self._platform, |
| + 'chrome-win32.test') |
| + dir_name = os.path.join(tempfile.gettempdir(), 'CHROMEDRIVER-' + build) |
| + if os.path.isdir(dir_name): |
| + self._chrome_driver = os.path.join(dir_name, 'chromedriver.exe') |
| + return True |
| + else: |
| + tmpdir = tempfile.mkdtemp() |
| + if self._Download('chromedriver.exe', url, tmpdir): |
| + shutil.move(tmpdir, dir_name) |
| + self._chrome_driver = os.path.join(dir_name, 'chromedriver.exe') |
| + return True |
| + return False |
| + |
| + def _DownloadDeps(self, build): |
| + """Downloads Chrome installer and ChromeDriver. |
| + |
| + Args: |
| + build: Chrome release build number. |
| + |
| + Returns: |
| + 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
|
| + """ |
| + url = '%s%s/%s' % (self._url, build, self._platform) |
| + dir_name = '%s%s' % (self._dir_prefix, build) |
| + location = os.path.join(tempfile.gettempdir(), dir_name) |
| + if not self._DownloadChromeDriver(): |
| + return False |
| + if os.path.isdir(location): |
| + self._download_dirs.append(location) |
| + return True |
| + else: |
| + tmpdir = tempfile.mkdtemp() |
| + if self._Download(self._installer, url, tmpdir): |
| + shutil.move(tmpdir, location) |
| + self._download_dirs.append(location) |
| + return True |
| + return False |
| + |
| + def _DeleteBuild(self): |
| + """Uninstalls Chrome.""" |
| + self._installation.Uninstall() |
| + |
| + def _DeleteDir(self, dir_name): |
| + """Deletes a directory. |
| + |
| + Args: |
| + dir_name: Name of the directory to delete. |
| + """ |
| + def _OnError(func, path, exc_info): |
| + """Callback for shutil.rmtree.""" |
| + if not os.access(path, os.W_OK): |
| + os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) |
| + func(path) |
| + |
| + if os.path.isdir(dir_name): |
| + shutil.rmtree(dir_name, onerror=_OnError) |
| + |
| + def _Download(self, dfile, url, dest=None): |
| + """Downloads a file from the specified URL. |
| + |
| + Args: |
| + dfile: Name of the file to download. |
| + url: URL where the file is located. |
| + dest: Location where file will be downloaded. Default is CWD. |
| + |
| + Returns: |
| + Zero if successful, otherwise a non-zero value. |
| + """ |
| + filename = ((dest and os.path.exists(dest)) and os.path.join(dest, dfile) |
| + or os.path.join(tempfile.gettempdir(), dfile)) |
| + file_url = '%s/%s' % (url, dfile) |
| + if not pyauto_utils.DoesUrlExist(file_url): |
| + raise RuntimeError('Either the URL or the file name is invalid.') |
| + try: |
| + dfile = urllib.urlretrieve(file_url, filename) |
| + 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.
|
| + raise err |
| + 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.
|
| + |
| + @staticmethod |
| + def SetOptions(opts): |
| + """Static method for passing command options to InstallTest. |
| + |
| + We do not instantiate InstallTest. Therefore, command arguments cannot |
| + be passed to its constructor. Since InstallTest needs to use these options |
| + and using globals is not an option, this method can be used by the Main |
| + class to pass the arguments it parses onto InstallTest. |
| + """ |
| + InstallTest._opts = opts |
| + |
| + |
| +class Main(object): |
| + """Main program for running Updater tests.""" |
| + |
| + _mod_path = sys.argv[0] |
| + |
| + def __init__(self): |
| + self._ParseArgs() |
| + self._Run() |
| + |
| + def _ParseArgs(self): |
| + """Parses command line arguments.""" |
| + parser = optparse.OptionParser() |
| + parser.add_option( |
| + '-b', '--builds', type='string', default='', dest='builds', |
| + 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
|
| + parser.add_option( |
| + '-u', '--url', type='string', default='', dest='url', |
| + help='Specifies the build url, without the build number.') |
| + parser.add_option( |
| + '-o', '--options', type='string', default='', |
| + help='Specifies any additional Chrome options (i.e. --system-level).') |
| + opts, args = parser.parse_args() |
| + 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.
|
| + self._ValidateArgs(opts) |
| + InstallTest.SetOptions(opts) |
| + |
| + def _ValidateArgs(self, opts): |
| + """Verifies the sanity of the command arguments. |
| + |
| + Confirms that all specified builds have a valid version number, and the |
| + build urls are valid. |
| + |
| + Args: |
| + opts: An object containing values for all command args. |
| + """ |
| + builds = opts.builds.split(',') |
| + for build in builds: |
| + if not re.match('\d+\.\d+\.\d+\.\d+', build): |
| + raise RuntimeError('Invalid build number: %s' % build) |
| + if not pyauto_utils.DoesUrlExist('%s/%s/' % (opts.url, build)): |
| + raise RuntimeError('Could not locate build no. %s' % build) |
| + |
| + def _SetLoggingConfiguration(self): |
| + """Sets the basic logging configuration.""" |
| + log_format = '%(asctime)s %(levelname)-8s %(message)s' |
| + logging.basicConfig(level=logging.INFO, format=log_format) |
| + |
| + def _GetTests(self): |
| + """Returns a list of unittests from the calling script.""" |
| + 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.
|
| + if os.path.dirname(self._mod_path) not in sys.path: |
| + sys.path.append(os.path.dirname(self._mod_path)) |
| + return unittest.defaultTestLoader.loadTestsFromNames(mod_name) |
| + |
| + def _Run(self): |
| + """Runs the unit tests.""" |
| + tests = self._GetTests() |
| + result = pyauto_utils.GTestTextTestRunner(verbosity=1).run(tests) |
| + del(tests) |
|
kkania
2012/10/02 17:05:16
?
nkang
2012/10/03 22:12:01
Done.
|
| + if not result.wasSuccessful(): |
| + print >>sys.stderr, ('Not all tests were successful.') |
| + sys.exit(1) |
| + sys.exit(0) |
| Property changes on: install_test\install_test.py |
| ___________________________________________________________________ |
| Added: svn:eol-style |
| + LF |