| Index: install_test/install_test.py
|
| ===================================================================
|
| --- install_test/install_test.py (revision 0)
|
| +++ install_test/install_test.py (revision 0)
|
| @@ -0,0 +1,407 @@
|
| +#!/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
|
| +level installations are supported, and either one can be used for running the
|
| +PyAuto tests. Currently the only platform that's supported is Windows.
|
| +"""
|
| +
|
| +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_win
|
| +from chrome_installer_win 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
|
| +
|
| +
|
| +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'])
|
| +
|
| +
|
| + 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
|
| + """
|
| +
|
| + _build_iterator = None
|
| + _current_build = ''
|
| + _current_location = ''
|
| + # Prefix that will be appended to the deps folder names.
|
| + _dir_prefix = '__CHRBLD__'
|
| + _dir_iterator = None
|
| + # Var that's populated with ChromeInstallation object on install/update.
|
| + _installation = None
|
| + _installer_name = 'mini_installer.exe'
|
| + # Var that's populated with PyUITest object on install/update.
|
| + _pyauto = None
|
| + # Locations in Temp directory where Chrome installers are downloaded.
|
| + _installers = []
|
| + # Contains paths to the deps folders.
|
| + _download_dirs = []
|
| + _opts = None
|
| +
|
| + def __init__(self, methodName='runTest'):
|
| + unittest.TestCase.__init__(self, methodName)
|
| + self._platform = InstallTest.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."""
|
| + 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()
|
| + 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()
|
| + self.assertTrue(self._pyauto)
|
| +
|
| + def tearDown(self):
|
| + """Called at the end of each unittest to do any test related cleanup."""
|
| + self._UnloadPyAutoModules()
|
| + self._DeleteBuild()
|
| +
|
| + @staticmethod
|
| + def GetPlatform():
|
| + """Returns the platform name."""
|
| + return ({'Windows': 'win',
|
| + 'Darwin': 'mac',
|
| + 'Linux': 'linux'}).get(platform.system())
|
| +
|
| + def _UnloadPyAutoModules(self):
|
| + """Deletes the PyUITest object and unloads PyAuto modules."""
|
| + if self._pyauto:
|
| + del self._pyauto
|
| + for module in ['pyauto', 'pyautolib', '_pyautolib']:
|
| + if module in sys.modules:
|
| + sys.modules.pop(module)
|
| +
|
| + def _RestorePaths(self):
|
| + """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_win.Install(installer_path,
|
| + self._install_type,
|
| + self._current_build,
|
| + self._options)
|
| + try:
|
| + import pyauto
|
| + except ImportError, err:
|
| + logging.error(err)
|
| + return
|
| + self._pyauto = pyauto.PyUITest(methodName='runTest',
|
| + browser_path=os.path.dirname(
|
| + self._installation.GetExePath()))
|
| + self._pyauto.suite_holder = pyauto.PyUITestSuite([])
|
| + self._pyauto.setUp()
|
| +
|
| +
|
| + def InstallBuild(self):
|
| + self._current_location = next(self._dir_iterator, None)
|
| + sys.path.insert(0, self._current_location)
|
| + sys.path.insert(1, os.path.join(self._current_location, 'pyautolib'))
|
| + self._Install()
|
| +
|
| + def _Update(self):
|
| + """Helper method for updating Chrome."""
|
| + self._RestorePaths()
|
| + 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 line args."""
|
| + if self._pyauto:
|
| + self._pyauto.TearDown()
|
| + self._UnloadPyAutoModules()
|
| + self._Update()
|
| +
|
| + def _SrcFilesExist(self, root, items):
|
| + """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 _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
|
| + 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)
|
| + dir_name = '%s%s' % (self._dir_prefix, build)
|
| + location = os.path.join(tempfile.gettempdir(), dir_name)
|
| + 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._RestorePaths()
|
| +
|
| + 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:
|
| + raise err
|
| + return os.path.isfile(dfile[0])
|
| +
|
| + @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.')
|
| + 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.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 _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, ('Not all tests were successful.')
|
| + sys.exit(1)
|
| + sys.exit(0)
|
|
|
| Property changes on: install_test\install_test.py
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|