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

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, 4 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,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

Powered by Google App Engine
This is Rietveld 408576698