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,396 @@ |
| +#!/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 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.
|
| +level installations are supported, and either one can be used for running the |
| +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.
|
| + |
| + |
| +Include the following in your updater test script to make it run standalone. |
| + |
| +from install_test import Main |
| + |
| +if __name__ == '__main__': |
| + Main() |
| +""" |
| + |
| +import httplib |
| +import logging |
| +import optparse |
| +import os |
| +import platform |
| +import re |
| +import shutil |
| +import stat |
| +import sys |
| +import tempfile |
| +import unittest |
| +import urllib |
| +import urlparse |
| + |
| +import chrome_checkout |
| +import chrome_installer |
| +from chrome_installer import ChromeInstallation |
| + |
| +sys.path.append(os.path.join(os.path.pardir, 'pyautolib')) |
| + |
| +# This import should go after sys.path is set appropriately. |
| +from fetch_prebuilt_pyauto import FetchPrebuilt |
| +import pyauto_utils |
| +from pyauto_utils import GTestTextTestRunner |
| + |
| +_OPTIONS = None |
| + |
| + |
| +class InstallTest(unittest.TestCase): |
| + """Base updater test class. |
| + |
| + All dependencies, such as the specified Chrome builds, source files, and |
| + installers are downloaded at the beginning of the test. Dependencies are |
| + downloaded in the temp directory. This download only occurs once, before |
| + the first test is executed. A PyUITest object is created whenever a user |
| + installs or updates Chrome, using dependencies that correspond with that |
| + particular build. Users can utilize that object to run updater tests. All |
| + updater tests should derive from this class. |
| + |
| + Example: |
| + |
| + class ProtectorUpdater(InstallTest): |
| + |
| + def testNoChangeOnCleanProfile(self): |
| + self.assertFalse(self._pyauto.GetProtectorState()['showing_change']) |
| + self.UpdateBuild() |
| + 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__'
|
| + """ |
| + |
| + _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.
|
| + _current_build = '' |
| + _current_location = '' |
| + _dir_prefix = '__CHRBLD__' |
| + _dir_iterator = None |
| + _installation = None |
| + _installer_name = 'mini_installer.exe' |
| + _pyauto = None |
| + _installers = [] |
| + _download_dirs = [] |
| + _opts = None |
| + |
| + def __init__(self, methodName='runTest'): |
| + unittest.TestCase.__init__(self, methodName) |
| + self._platform = self._GetPlatform() |
| + self._Initialize() |
| + current_build = ChromeInstallation.GetCurrent() |
| + if current_build: |
| + current_build.Uninstall() |
| + for build in self._builds: |
| + if not self._DownloadDeps(build): |
| + raise RuntimeError('Could not download dependencies.') |
| + self._installer_iter = iter(self._installers) |
| + self._dir_iterator = iter(self._download_dirs) |
| + |
| + def _Initialize(self): |
| + """Sets test parameters.""" |
| + global _OPTIONS |
| + self._url = _OPTIONS.url |
| + self._builds = _OPTIONS.builds and _OPTIONS.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() |
| + self._url = self._url.endswith('/') and self._url or self._url + '/' |
| + self._options = (_OPTIONS.options and _OPTIONS.options.replace(',', ' ') |
| + or '') |
| + self._install_type = ('system-level' in self._options and |
| + chrome_installer.InstallationType.SYSTEM or |
| + chrome_installer.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() |
| + 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.
|
| + |
| + def tearDown(self): |
| + """Called at the end of each unittest to do any test related cleanup.""" |
| + self._ClearModulesRegistry() |
| + self._DeleteBuild() |
| + |
| + 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
|
| + """Returns the platform name.""" |
| + return ({'Windows': 'win', |
| + 'Darwin': 'mac', |
| + 'Linux': 'linux'}).get(platform.system()) |
| + |
| + 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
|
| + """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.
|
| + if self._pyauto: |
| + del self._pyauto |
| + for module in ['pyauto', 'pyautolib', '_pyautolib']: |
| + if module in sys.modules: |
| + sys.modules.pop(module) |
| + |
| + 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 ;)
|
| + """Restores the sys.path variable to its original state.""" |
| + sys.path = list(frozenset(sys.path)) |
| + if self._current_location: |
| + if self._current_location in sys.path: |
| + sys.path.remove(self._current_location) |
| + if os.path.join(self._current_location, 'pyautolib') in sys.path: |
| + sys.path.remove(os.path.join(self._current_location, 'pyautolib')) |
| + |
| + def _Install(self): |
| + """Helper method that installs Chrome and creates a PyUITest object.""" |
| + self._pyauto = None |
| + installer_path = next(self._installer_iter, None) |
| + if not installer_path: |
| + raise RuntimeError('No more builds left to install.') |
| + self._installation = chrome_installer.Install(installer_path, |
| + self._install_type, |
| + self._current_build, |
| + self._options) |
| + try: |
| + 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.
|
| + self._pyauto = pyauto.PyUITest(methodName='runTest', |
| + browser_path=os.path.dirname( |
| + self._installation.GetExePath())) |
| + self._pyauto.suite_holder = pyauto.PyUITestSuite([]) |
| + self._pyauto.setUp() |
| + except ImportError, err: |
| + 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.
|
| + self._pyauto = None |
| + |
| + def InstallBuild(self): |
| + self._current_location = next(self._dir_iterator, None) |
| + 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
|
| + sys.path.insert(1, os.path.join(self._current_location, 'pyautolib')) |
| + self._Install() |
| + |
| + def _Update(self): |
| + """Helper method for updating Chrome.""" |
| + self._RemovePaths() |
| + self._current_location = next(self._dir_iterator, None) |
| + assert (self._current_location) |
| + sys.path.insert(0, self._current_location) |
| + sys.path.insert(1, os.path.join(self._current_location, 'pyautolib')) |
| + 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() |
| + |
| + def UpdateBuild(self): |
| + """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.
|
| + if self._pyauto: |
| + self._pyauto.TearDown() |
| + self._ClearModulesRegistry() |
| + self._Update() |
| + |
| + def _SrcFilesExist(self, root, items): |
| + """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.
|
| + |
| + 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(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.
|
| + [os.path.join(root, path) for path in items])) |
| + |
| + def _CheckoutSourceFiles(self, build, location): |
| + """Checks out source files associated with the current build. |
| + |
| + Args: |
| + build: Chrome release version number. |
| + location: Destination where source files will be saved. |
| + Returns: |
| + Zero if successful, otherwise a negative value. |
| + """ |
| + try: |
| + chrome_checkout.CheckOut(build, location) |
| + return True |
| + except AssertionError: |
| + return False |
| + |
| + def _FetchPrebuiltPyauto(self, url, location): |
| + """Fetches the specified Chrome build. |
| + |
| + Args: |
| + url: URL where the build is located. |
| + location: Location where the build will be downloaded. |
| + |
| + Returns: |
| + True if successful, otherwise False. |
| + """ |
| + fetch_build = FetchPrebuilt(url, location, self._platform) |
| + if pyauto_utils.DoesUrlExist(url): |
| + return fetch_build.Run() == 0 |
| + 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.
|
| + return False |
| + |
| + def _DownloadInstaller(self, url, location): |
| + """Downloads the Chrome installer. |
| + |
| + Args: |
| + url: URL where the installer is located. |
| + location: Location where installer will be downloaded. |
| + |
| + Returns: |
| + True if successful, otherwise False. |
| + """ |
| + try: |
| + self._Download(self._installer_name, url, location) |
| + return True |
| + except (IOError, RuntimeError): |
| + return False |
| + |
| + def _DownloadDeps(self, build): |
| + """Downloads Chrome build, source files, and Chrome installer. |
| + |
| + Args: |
| + build: Chrome release build number. |
| + |
| + Returns: |
| + True if successful, otherwise False. |
| + """ |
| + url = '%s%s/%s' % (self._url, build, self._platform) |
| + 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
|
| + self._dir_prefix, build) |
| + if os.path.isdir(location): |
| + self._download_dirs.append(location) |
| + self._installers.append(os.path.join(location, self._installer_name)) |
| + return True |
| + else: |
| + tmpdir = tempfile.mkdtemp() |
| + if self._CheckoutSourceFiles(build, tmpdir): |
| + if self._FetchPrebuiltPyauto(url, tmpdir): |
| + if self._DownloadInstaller(url, tmpdir): |
| + try: |
| + # This is a workaround because rename was causing problems. |
| + shutil.copytree(tmpdir, location) |
| + # Callback is there because hidden svn files are read-only, so |
| + # we need to change their permissions on the fly to delete them. |
| + self._DeleteDir(tmpdir) |
| + self._download_dirs.append(location) |
| + self._installers.append(os.path.join(location, |
| + self._installer_name)) |
| + return True |
| + except(OSError, IOError), err: |
| + return False |
| + return False |
| + |
| + def _DeleteBuild(self): |
| + """Uninstalls Chrome.""" |
| + self._installation.Uninstall() |
| + self._build_iterator = iter(self._builds) |
| + self._dir_iterator = iter(self._download_dirs) |
| + self._current_build = next(self._build_iterator, None) |
| + self._current_location = next(self._dir_iterator, None) |
| + self._RemovePaths() |
| + |
| + 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: |
| + 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),
|
| + except IOError, err: |
| + raise err |
| + return os.path.isfile(d[0]) |
| + |
| + |
| +class Main(object): |
| + """Main program for running Updater tests.""" |
| + |
| + _tests_file = 'PYAUTO_TESTS' |
| + _mod_path = sys.argv[0] |
| + _pyauto_doc_url = 'http://dev.chromium.org/developers/testing/pyauto' |
| + |
| + def __init__(self): |
| + self._ParseArgs() |
| + self._Run() |
| + |
| + def _ParseArgs(self): |
| + """Parses command line arguments.""" |
| + global _OPTIONS |
| + parser = optparse.OptionParser() |
| + parser.add_option( |
| + '-b', '--builds', type='string', default='', dest='builds', |
| + help='Specifies the two (or more) builds needed for testing.') |
| + 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() |
| + _OPTIONS = opts |
| + self.ValidateArgs(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 _GetTests(self): |
| + """Returns a list of unittests from the calling script.""" |
| + mod_name = [os.path.splitext(os.path.basename(self._mod_path))[0]] |
| + 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 = GTestTextTestRunner(verbosity=1).run(tests) |
| + del(tests) |
| + if not result.wasSuccessful(): |
| + 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'
|
| + % (self._tests_file, self._pyauto_doc_url)) |
| + sys.exit(1) |
| + sys.exit(0) |
| Property changes on: install_test\install_test.py |
| ___________________________________________________________________ |
| Added: svn:eol-style |
| + LF |