Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(16)

Unified Diff: install_test/install_test.py

Issue 10384104: Chrome updater test framework (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/chrome/test/
Patch Set: Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698