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

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, 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 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 PyAuto tests using the installed version. User and system
10 level installations are supported, and either one can be used for running the
11 PyAuto tests. Currently the only platform that's supported is Windows.
12 """
13
14 import httplib
15 import logging
16 import optparse
17 import os
18 import platform
19 import re
20 import shutil
21 import stat
22 import sys
23 import tempfile
24 import unittest
25 import urllib
26 import urlparse
27
28 import chrome_checkout
29 import chrome_installer_win
30 from chrome_installer_win import ChromeInstallation
31
32 sys.path.append(os.path.join(os.path.pardir, 'pyautolib'))
33
34 # This import should go after sys.path is set appropriately.
35 from fetch_prebuilt_pyauto import FetchPrebuilt
36 import pyauto_utils
37 from pyauto_utils import GTestTextTestRunner
38
39
40 class InstallTest(unittest.TestCase):
41 """Base updater test class.
42
43 All dependencies, such as the specified Chrome builds, source files, and
44 installers are downloaded at the beginning of the test. Dependencies are
45 downloaded in the temp directory. This download only occurs once, before
46 the first test is executed. A PyUITest object is created whenever a user
47 installs or updates Chrome, using dependencies that correspond with that
48 particular build. Users can utilize that object to run updater tests. All
49 updater tests should derive from this class.
50
51 Example:
52
53 class ProtectorUpdater(InstallTest):
54
55 def testNoChangeOnCleanProfile(self):
56 self.assertFalse(self._pyauto.GetProtectorState()['showing_change'])
57 self.UpdateBuild()
58 self.assertFalse(self._pyauto.GetProtectorState()['showing_change'])
59
60
61 Include the following in your updater test script to make it run standalone.
62
63 from install_test import Main
64
65 if __name__ == '__main__':
66 Main()
67
68 To fire off an updater test, use the command below.
69 python test_script.py --url=<URL> --builds=22.0.1230.0,22.0.1231.0
70 """
71
72 _build_iterator = None
73 _current_build = ''
74 _current_location = ''
75 # Prefix that will be appended to the deps folder names.
76 _dir_prefix = '__CHRBLD__'
77 _dir_iterator = None
78 # Var that's populated with ChromeInstallation object on install/update.
79 _installation = None
80 _installer_name = 'mini_installer.exe'
81 # Var that's populated with PyUITest object on install/update.
82 _pyauto = None
83 # Locations in Temp directory where Chrome installers are downloaded.
84 _installers = []
85 # Contains paths to the deps folders.
86 _download_dirs = []
87 _opts = None
88
89 def __init__(self, methodName='runTest'):
90 unittest.TestCase.__init__(self, methodName)
91 self._platform = InstallTest.GetPlatform()
92 self._Initialize()
93 current_build = ChromeInstallation.GetCurrent()
94 if current_build:
95 current_build.Uninstall()
96 for build in self._builds:
97 if not self._DownloadDeps(build):
98 raise RuntimeError('Could not download dependencies.')
99 self._installer_iter = iter(self._installers)
100 self._dir_iterator = iter(self._download_dirs)
101
102 def _Initialize(self):
103 """Sets test parameters."""
104 self._url = self._opts.url
105 self._builds = self._opts.builds and self._opts.builds.split(',') or []
106 if not self._url or not self._builds:
107 raise RuntimeError('Please specify a valid URL and two Chrome builds.')
108 self._builds.sort()
109 self._url = self._url.endswith('/') and self._url or self._url + '/'
110 self._options = self._opts.options.split(',') if self._opts.options else []
111 self._install_type = ('system-level' in self._options and
112 chrome_installer_win.InstallationType.SYSTEM or
113 chrome_installer_win.InstallationType.USER)
114 self._build_iterator = iter(self._builds)
115 self._current_build = next(self._build_iterator, None)
116
117 def setUp(self):
118 """Called before each unittest to prepare the test fixture."""
119 self.InstallBuild()
120 self.assertTrue(self._pyauto)
121
122 def tearDown(self):
123 """Called at the end of each unittest to do any test related cleanup."""
124 self._UnloadPyAutoModules()
125 self._DeleteBuild()
126
127 @staticmethod
128 def GetPlatform():
129 """Returns the platform name."""
130 return ({'Windows': 'win',
131 'Darwin': 'mac',
132 'Linux': 'linux'}).get(platform.system())
133
134 def _UnloadPyAutoModules(self):
135 """Deletes the PyUITest object and unloads PyAuto modules."""
136 if self._pyauto:
137 del self._pyauto
138 for module in ['pyauto', 'pyautolib', '_pyautolib']:
139 if module in sys.modules:
140 sys.modules.pop(module)
141
142 def _RestorePaths(self):
143 """Restores the sys.path variable to its original state."""
144 sys.path = list(frozenset(sys.path))
145 if self._current_location:
146 if self._current_location in sys.path:
147 sys.path.remove(self._current_location)
148 if os.path.join(self._current_location, 'pyautolib') in sys.path:
149 sys.path.remove(os.path.join(self._current_location, 'pyautolib'))
150
151 def _Install(self):
152 """Helper method that installs Chrome and creates a PyUITest object."""
153 self._pyauto = None
154 installer_path = next(self._installer_iter, None)
155 if not installer_path:
156 raise RuntimeError('No more builds left to install.')
157 self._installation = chrome_installer_win.Install(installer_path,
158 self._install_type,
159 self._current_build,
160 self._options)
161 try:
162 import pyauto
163 except ImportError, err:
164 logging.error(err)
165 return
166 self._pyauto = pyauto.PyUITest(methodName='runTest',
167 browser_path=os.path.dirname(
168 self._installation.GetExePath()))
169 self._pyauto.suite_holder = pyauto.PyUITestSuite([])
170 self._pyauto.setUp()
171
172
173 def InstallBuild(self):
174 self._current_location = next(self._dir_iterator, None)
175 sys.path.insert(0, self._current_location)
176 sys.path.insert(1, os.path.join(self._current_location, 'pyautolib'))
177 self._Install()
178
179 def _Update(self):
180 """Helper method for updating Chrome."""
181 self._RestorePaths()
182 self._current_location = next(self._dir_iterator, None)
183 assert (self._current_location)
184 sys.path.insert(0, self._current_location)
185 sys.path.insert(1, os.path.join(self._current_location, 'pyautolib'))
186 build = next(self._build_iterator, None)
187 if not build:
188 raise RuntimeError('No more builds left to install. Following builds '
189 'have already been installed: %r' % self._builds)
190 self._current_build = build
191 self._Install()
192
193 def UpdateBuild(self):
194 """Installs the second Chrome build specified in the command line args."""
195 if self._pyauto:
196 self._pyauto.TearDown()
197 self._UnloadPyAutoModules()
198 self._Update()
199
200 def _SrcFilesExist(self, root, items):
201 """Checks if specified files/folders exist at the specified location.
202
203 Args:
204 root: Parent folder where all the source directories reside.
205 items: List of files/folders to be verified for existence in the root.
206
207 Returns:
208 True, if all sub-folders exist in the root, otherwise False.
209 """
210 return all([os.path.exists(os.path.join(root, x)) for x in items])
211
212 def _CheckoutSourceFiles(self, build, location):
213 """Checks out source files associated with the current build.
214
215 Args:
216 build: Chrome release version number.
217 location: Destination where source files will be saved.
218 Returns:
219 Zero if successful, otherwise a negative value.
220 """
221 try:
222 chrome_checkout.CheckOut(build, location)
223 return True
224 except AssertionError:
225 return False
226
227 def _FetchPrebuiltPyauto(self, url, location):
228 """Fetches the specified Chrome build.
229
230 Args:
231 url: URL where the build is located.
232 location: Location where the build will be downloaded.
233
234 Returns:
235 True if successful, otherwise False.
236 """
237 fetch_build = FetchPrebuilt(url, location, self._platform)
238 if pyauto_utils.DoesUrlExist(url):
239 return fetch_build.Run() == 0
240 return False
241
242 def _DownloadInstaller(self, url, location):
243 """Downloads the Chrome installer.
244
245 Args:
246 url: URL where the installer is located.
247 location: Location where installer will be downloaded.
248
249 Returns:
250 True if successful, otherwise False.
251 """
252 try:
253 self._Download(self._installer_name, url, location)
254 return True
255 except (IOError, RuntimeError):
256 return False
257
258 def _DownloadDeps(self, build):
259 """Downloads Chrome build, source files, and Chrome installer.
260
261 Args:
262 build: Chrome release build number.
263
264 Returns:
265 True if successful, otherwise False.
266 """
267 url = '%s%s/%s' % (self._url, build, self._platform)
268 dir_name = '%s%s' % (self._dir_prefix, build)
269 location = os.path.join(tempfile.gettempdir(), dir_name)
270 if os.path.isdir(location):
271 self._download_dirs.append(location)
272 self._installers.append(os.path.join(location, self._installer_name))
273 return True
274 else:
275 tmpdir = tempfile.mkdtemp()
276 if self._CheckoutSourceFiles(build, tmpdir):
277 if self._FetchPrebuiltPyauto(url, tmpdir):
278 if self._DownloadInstaller(url, tmpdir):
279 try:
280 # This is a workaround because rename was causing problems.
281 shutil.copytree(tmpdir, location)
282 # Callback is there because hidden svn files are read-only, so
283 # we need to change their permissions on the fly to delete them.
284 self._DeleteDir(tmpdir)
285 self._download_dirs.append(location)
286 self._installers.append(os.path.join(location,
287 self._installer_name))
288 return True
289 except(OSError, IOError), err:
290 return False
291 return False
292
293 def _DeleteBuild(self):
294 """Uninstalls Chrome."""
295 self._installation.Uninstall()
296 self._build_iterator = iter(self._builds)
297 self._dir_iterator = iter(self._download_dirs)
298 self._current_build = next(self._build_iterator, None)
299 self._current_location = next(self._dir_iterator, None)
300 self._RestorePaths()
301
302 def _DeleteDir(self, dir_name):
303 """Deletes a directory.
304
305 Args:
306 dir_name: Name of the directory to delete.
307 """
308 def _OnError(func, path, exc_info):
309 """Callback for shutil.rmtree."""
310 if not os.access(path, os.W_OK):
311 os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
312 func(path)
313
314 if os.path.isdir(dir_name):
315 shutil.rmtree(dir_name, onerror=_OnError)
316
317 def _Download(self, dfile, url, dest=None):
318 """Downloads a file from the specified URL.
319
320 Args:
321 dfile: Name of the file to download.
322 url: URL where the file is located.
323 dest: Location where file will be downloaded. Default is CWD.
324
325 Returns:
326 Zero if successful, otherwise a non-zero value.
327 """
328 filename = ((dest and os.path.exists(dest)) and os.path.join(dest, dfile)
329 or os.path.join(tempfile.gettempdir(), dfile))
330 file_url = '%s/%s' % (url, dfile)
331 if not pyauto_utils.DoesUrlExist(file_url):
332 raise RuntimeError('Either the URL or the file name is invalid.')
333 try:
334 dfile = urllib.urlretrieve(file_url, filename)
335 except IOError, err:
336 raise err
337 return os.path.isfile(dfile[0])
338
339 @staticmethod
340 def SetOptions(opts):
341 """Static method for passing command options to InstallTest.
342
343 We do not instantiate InstallTest. Therefore, command arguments cannot
344 be passed to its constructor. Since InstallTest needs to use these options
345 and using globals is not an option, this method can be used by the Main
346 class to pass the arguments it parses onto InstallTest.
347 """
348 InstallTest._opts = opts
349
350
351 class Main(object):
352 """Main program for running Updater tests."""
353
354 _mod_path = sys.argv[0]
355
356 def __init__(self):
357 self._ParseArgs()
358 self._Run()
359
360 def _ParseArgs(self):
361 """Parses command line arguments."""
362 parser = optparse.OptionParser()
363 parser.add_option(
364 '-b', '--builds', type='string', default='', dest='builds',
365 help='Specifies the two (or more) builds needed for testing.')
366 parser.add_option(
367 '-u', '--url', type='string', default='', dest='url',
368 help='Specifies the build url, without the build number.')
369 parser.add_option(
370 '-o', '--options', type='string', default='',
371 help='Specifies any additional Chrome options (i.e. --system-level).')
372 opts, args = parser.parse_args()
373 self.ValidateArgs(opts)
374 InstallTest.SetOptions(opts)
375
376 def ValidateArgs(self, opts):
377 """Verifies the sanity of the command arguments.
378
379 Confirms that all specified builds have a valid version number, and the
380 build urls are valid.
381
382 Args:
383 opts: An object containing values for all command args.
384 """
385 builds = opts.builds.split(',')
386 for build in builds:
387 if not re.match('\d+\.\d+\.\d+\.\d+', build):
388 raise RuntimeError('Invalid build number: %s' % build)
389 if not pyauto_utils.DoesUrlExist('%s/%s/' % (opts.url, build)):
390 raise RuntimeError('Could not locate build no. %s' % build)
391
392 def _GetTests(self):
393 """Returns a list of unittests from the calling script."""
394 mod_name = [os.path.splitext(os.path.basename(self._mod_path))[0]]
395 if os.path.dirname(self._mod_path) not in sys.path:
396 sys.path.append(os.path.dirname(self._mod_path))
397 return unittest.defaultTestLoader.loadTestsFromNames(mod_name)
398
399 def _Run(self):
400 """Runs the unit tests."""
401 tests = self._GetTests()
402 result = GTestTextTestRunner(verbosity=1).run(tests)
403 del(tests)
404 if not result.wasSuccessful():
405 print >>sys.stderr, ('Not all tests were successful.')
406 sys.exit(1)
407 sys.exit(0)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698