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

Side by Side 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, 2 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 unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Test fixture for tests involving installing/updating Chrome.
7
8 Provides an interface to install or update chrome from within a testcase, and
9 allows users to run tests using installed version of Chrome. User and system
10 level installations are supported, and either one can be used for running the
11 tests. Currently the only platform it supports is Windows.
12 """
13
14 import logging
15 import optparse
16 import os
17 import platform
18 import re
19 import shutil
20 import stat
21 import sys
22 import tempfile
23 import unittest
24 import urllib
25
26 import chrome_installer_win
27
28 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.
29
30 # This import should go after sys.path is set appropriately.
31 import pyauto_utils
32 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.
33 from selenium import webdriver
34
35
36 class InstallTest(unittest.TestCase):
37 """Base updater test class.
38
39 All dependencies, like Chrome installers and ChromeDriver, are downloaded at
40 the beginning of the test. Dependencies are downloaded in the temp directory.
41 This download occurs only once, before the first test is executed. Each test
42 case starts an instance of ChromeDriver and terminates it upon completion.
43 All updater tests should derive from this class.
44
45 Example:
46
47 class ProtectorUpdater(InstallTest):
48
49 def testCanOpenGoogle(self):
50 self._driver.get('http://www.google.com/')
51 self.UpdateBuild()
52 self._driver.get('http://www.msn.com/')
53
54 Include the following in your updater test script to make it run standalone.
55
56 from install_test import Main
57
58 if __name__ == '__main__':
59 Main()
60
61 To fire off an updater test, use the command below.
62 python test_script.py --url=<URL> --builds=22.0.1230.0,22.0.1231.0
63 """
64
65 _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
66 _installer = 'mini_installer.exe'
67 _download_dirs = []
68 _opts = None
69 _chrome_driver = ''
70
71 def __init__(self, methodName='runTest'):
72 unittest.TestCase.__init__(self, methodName)
73 self._platform = InstallTest.GetPlatform()
74 self._Initialize()
75 current_build = chrome_installer_win.ChromeInstallation.GetCurrent()
76 if current_build:
77 current_build.Uninstall()
78 for build in self._builds:
79 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
80 raise RuntimeError('Could not download dependencies.')
81 self._dir_iterator = iter(self._download_dirs)
82
83 def _Initialize(self):
84 """Sets test parameters."""
85 self._url = self._opts.url
86 self._builds = self._opts.builds and self._opts.builds.split(',') or []
87 if not self._url or not self._builds:
88 raise RuntimeError('Please specify a valid URL and two Chrome builds.')
89 self._builds.sort()
kkania 2012/10/02 17:05:16 don't sort
nkang 2012/10/03 22:12:01 Got rid of it.
90 self._url = self._url.endswith('/') and self._url or self._url + '/'
91 self._options = self._opts.options.split(',') if self._opts.options else []
92 self._install_type = ('system-level' in self._options and
93 chrome_installer_win.InstallationType.SYSTEM or
94 chrome_installer_win.InstallationType.USER)
95 self._build_iterator = iter(self._builds)
96 self._current_build = next(self._build_iterator, None)
97
98 def setUp(self):
99 """Called before each unittest to prepare the test fixture."""
100 self.InstallBuild()
101 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.
102 self._StartChromeDriver(self._chrome_driver,
103 self._installation.GetExePath())
104
105 def tearDown(self):
106 """Called at the end of each unittest to do any test related cleanup."""
107 self._ShutDownChromeDriver()
108 self._DeleteBuild()
109
110 def _StartChromeDriver(self, chrome_driver_location, chrome_location):
111 """Starts ChromeDriver.
112
113 Args:
114 chrome_driver_locations: Location of ChromeDriver executable.
115 chrome_location: Location of the Chrome binary.
116 """
117 self._service = service.Service(chrome_driver_location)
118 self._service.start()
119 self._capabilities = {'chrome.binary' : chrome_location}
120 self._driver = webdriver.Remote(self._service.service_url,
121 self._capabilities)
122
123 def _ShutDownChromeDriver(self):
124 """Shuts down ChromeDriver."""
125 self._driver.quit()
126 self._service.stop()
127
128 @staticmethod
129 def GetPlatform():
130 """Returns the platform name."""
131 return ({'Windows': 'win',
132 'Darwin': 'mac',
133 'Linux': 'linux'}).get(platform.system())
134
135 def _Install(self):
136 """Helper method that installs Chrome."""
137 installer_path = os.path.join(self._current_location, self._installer)
138 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!
139 raise RuntimeError('No more builds left to install.')
140 self._installation = chrome_installer_win.Install(installer_path,
141 self._install_type,
142 self._current_build,
143 self._options)
144
145 def InstallBuild(self):
146 """Installs a Chrome build."""
147 self._current_location = next(self._dir_iterator, None)
148 self._Install()
149
150 def UpdateBuild(self):
151 """Updates Chrome by installing a newer version."""
152 self._driver.quit()
153 self._current_location = next(self._dir_iterator, None)
154 assert(self._current_location)
155 build = next(self._build_iterator, None)
156 if not build:
157 raise RuntimeError('No more builds left to install. Following builds '
158 'have already been installed: %r' % self._builds)
159 self._current_build = build
160 self._Install()
161 self._driver = webdriver.Remote(self._service.service_url,
162 self._capabilities)
163
164 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
165 """Checks if specified files/folders exist at the specified location.
166
167 Args:
168 root: Parent folder where all the source directories reside.
169 items: List of files/folders to be verified for existence in the root.
170
171 Returns:
172 True, if all sub-folders exist in the root, otherwise False.
173 """
174 return all([os.path.exists(os.path.join(root, x)) for x in items])
175
176 def _DownloadChromeDriver(self):
177 """Downloads ChromeDriver executable.
178
179 Returns:
180 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
181 """
182 build = max(self._builds)
183 url = '%s%s/%s/%s/' % (self._url, build, self._platform,
184 'chrome-win32.test')
185 dir_name = os.path.join(tempfile.gettempdir(), 'CHROMEDRIVER-' + build)
186 if os.path.isdir(dir_name):
187 self._chrome_driver = os.path.join(dir_name, 'chromedriver.exe')
188 return True
189 else:
190 tmpdir = tempfile.mkdtemp()
191 if self._Download('chromedriver.exe', url, tmpdir):
192 shutil.move(tmpdir, dir_name)
193 self._chrome_driver = os.path.join(dir_name, 'chromedriver.exe')
194 return True
195 return False
196
197 def _DownloadDeps(self, build):
198 """Downloads Chrome installer and ChromeDriver.
199
200 Args:
201 build: Chrome release build number.
202
203 Returns:
204 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
205 """
206 url = '%s%s/%s' % (self._url, build, self._platform)
207 dir_name = '%s%s' % (self._dir_prefix, build)
208 location = os.path.join(tempfile.gettempdir(), dir_name)
209 if not self._DownloadChromeDriver():
210 return False
211 if os.path.isdir(location):
212 self._download_dirs.append(location)
213 return True
214 else:
215 tmpdir = tempfile.mkdtemp()
216 if self._Download(self._installer, url, tmpdir):
217 shutil.move(tmpdir, location)
218 self._download_dirs.append(location)
219 return True
220 return False
221
222 def _DeleteBuild(self):
223 """Uninstalls Chrome."""
224 self._installation.Uninstall()
225
226 def _DeleteDir(self, dir_name):
227 """Deletes a directory.
228
229 Args:
230 dir_name: Name of the directory to delete.
231 """
232 def _OnError(func, path, exc_info):
233 """Callback for shutil.rmtree."""
234 if not os.access(path, os.W_OK):
235 os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
236 func(path)
237
238 if os.path.isdir(dir_name):
239 shutil.rmtree(dir_name, onerror=_OnError)
240
241 def _Download(self, dfile, url, dest=None):
242 """Downloads a file from the specified URL.
243
244 Args:
245 dfile: Name of the file to download.
246 url: URL where the file is located.
247 dest: Location where file will be downloaded. Default is CWD.
248
249 Returns:
250 Zero if successful, otherwise a non-zero value.
251 """
252 filename = ((dest and os.path.exists(dest)) and os.path.join(dest, dfile)
253 or os.path.join(tempfile.gettempdir(), dfile))
254 file_url = '%s/%s' % (url, dfile)
255 if not pyauto_utils.DoesUrlExist(file_url):
256 raise RuntimeError('Either the URL or the file name is invalid.')
257 try:
258 dfile = urllib.urlretrieve(file_url, filename)
259 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.
260 raise err
261 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.
262
263 @staticmethod
264 def SetOptions(opts):
265 """Static method for passing command options to InstallTest.
266
267 We do not instantiate InstallTest. Therefore, command arguments cannot
268 be passed to its constructor. Since InstallTest needs to use these options
269 and using globals is not an option, this method can be used by the Main
270 class to pass the arguments it parses onto InstallTest.
271 """
272 InstallTest._opts = opts
273
274
275 class Main(object):
276 """Main program for running Updater tests."""
277
278 _mod_path = sys.argv[0]
279
280 def __init__(self):
281 self._ParseArgs()
282 self._Run()
283
284 def _ParseArgs(self):
285 """Parses command line arguments."""
286 parser = optparse.OptionParser()
287 parser.add_option(
288 '-b', '--builds', type='string', default='', dest='builds',
289 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
290 parser.add_option(
291 '-u', '--url', type='string', default='', dest='url',
292 help='Specifies the build url, without the build number.')
293 parser.add_option(
294 '-o', '--options', type='string', default='',
295 help='Specifies any additional Chrome options (i.e. --system-level).')
296 opts, args = parser.parse_args()
297 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.
298 self._ValidateArgs(opts)
299 InstallTest.SetOptions(opts)
300
301 def _ValidateArgs(self, opts):
302 """Verifies the sanity of the command arguments.
303
304 Confirms that all specified builds have a valid version number, and the
305 build urls are valid.
306
307 Args:
308 opts: An object containing values for all command args.
309 """
310 builds = opts.builds.split(',')
311 for build in builds:
312 if not re.match('\d+\.\d+\.\d+\.\d+', build):
313 raise RuntimeError('Invalid build number: %s' % build)
314 if not pyauto_utils.DoesUrlExist('%s/%s/' % (opts.url, build)):
315 raise RuntimeError('Could not locate build no. %s' % build)
316
317 def _SetLoggingConfiguration(self):
318 """Sets the basic logging configuration."""
319 log_format = '%(asctime)s %(levelname)-8s %(message)s'
320 logging.basicConfig(level=logging.INFO, format=log_format)
321
322 def _GetTests(self):
323 """Returns a list of unittests from the calling script."""
324 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.
325 if os.path.dirname(self._mod_path) not in sys.path:
326 sys.path.append(os.path.dirname(self._mod_path))
327 return unittest.defaultTestLoader.loadTestsFromNames(mod_name)
328
329 def _Run(self):
330 """Runs the unit tests."""
331 tests = self._GetTests()
332 result = pyauto_utils.GTestTextTestRunner(verbosity=1).run(tests)
333 del(tests)
kkania 2012/10/02 17:05:16 ?
nkang 2012/10/03 22:12:01 Done.
334 if not result.wasSuccessful():
335 print >>sys.stderr, ('Not all tests were successful.')
336 sys.exit(1)
337 sys.exit(0)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698