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

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, 5 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 import httplib
7 import logging
8 import optparse
9 import os
10 import platform
11 import shutil
12 import stat
13 import sys
14 import tempfile
15 import unittest
16 import urllib
17 import urlparse
18
19 import chrome_checkout
20 import chrome_installer
21 from py_unittest_util import GTestTextTestRunner
22
23 sys.path.append(os.path.join(os.path.pardir, 'pyautolib'))
24
25 # This import should go after sys.path is set appropriately.
26 from fetch_prebuilt_pyauto import FetchPrebuilt
27
28 _OPTIONS = None
29
30
31 class InstallTest(unittest.TestCase):
32 """Test fixture for tests involving installing/updating Chrome.
33
34 Provides an interface to install or update chrome from within a testcase, and
35 allows users to run pyauto tests using the installed version. User and system
36 level installations are supported, and either one can be used for running the
37 pyauto tests. Pyautolib files are downloaded at runtime and a PyUITest object
38 is created when Chrome is installed or updated. Users can utilize that object
39 to run updater tests. All Updater tests should derive from this class.
40 """
41
42 _build_iterator = None
43 _current_build = ''
44 _current_location = None
45 _dir_prefix = '__CHRBLD__'
46 _dir_iterator = None
47 _installer_name = 'mini_installer.exe'
48 _pyauto = None
49 _installers = []
50 _download_dirs = []
51
52 def __init__(self, methodName='runTest'):
53 unittest.TestCase.__init__(self, methodName)
54 self._platform = self._GetPlatform()
55 self._Initialize()
56 if self._installation.GetChromeInstallationType():
57 self._DeleteBuild()
58 for build in self._builds:
59 if not self._DownloadDeps(build):
60 raise RuntimeError('Could not download dependencies.')
61 self._installer_iter = iter(self._installers)
62 self._dir_iterator = iter(self._download_dirs)
63
64 def _Initialize(self):
65 """Sets test parameters."""
66 global _OPTIONS
67 self._url = _OPTIONS.url
68 self._builds = _OPTIONS.builds and _OPTIONS.builds.split(',') or []
69 if not self._url or not self._builds:
70 raise RuntimeError('Please specify a valid URL and two Chrome builds.')
71 self._builds.sort()
72 self._url = self._url.endswith('/') and self._url or self._url + '/'
73 self._dir = os.path.isdir(_OPTIONS.dir) and _OPTIONS.dir or os.getcwd()
74 self._options = (_OPTIONS.options and _OPTIONS.options.replace(',', ' ')
75 or '')
76 self._install_type = ('system-level' in self._options and
77 chrome_installer.InstallationType.SYSTEM or
78 chrome_installer.InstallationType.USER)
79 self._installation = chrome_installer.Installation(self._install_type)
80 self._build_iterator = iter(self._builds)
81 self._current_build = next(self._build_iterator, None)
82
83 def setUp(self):
84 """Called before each unittest to prepare the test fixture."""
85 self.InstallBuild()
86 self.failIf(self._pyauto == None)
87
88 def tearDown(self):
89 """Called at the end of each unittest to do any test related cleanup."""
90 self._Refresh()
91 self._DeleteBuild()
92
93 def _GetPlatform(self):
94 """Returns the platform name."""
95 return ({'Windows': 'win',
96 'Darwin': 'mac',
97 'Linux': 'linux'}).get(platform.system())
98
99 def _Refresh(self):
100 """Deletes the PyUITest object and clears the modules registry."""
101 if self._pyauto:
102 del(self._pyauto)
103 for module in ['pyauto', 'pyautolib', '_pyautolib']:
104 if module in sys.modules:
105 sys.modules.pop(module)
106
107 def _RemovePaths(self):
108 """Restores the sys.path variable to its original state."""
109 sys.path = list(frozenset(sys.path))
110 if self._current_location in sys.path:
111 sys.path.remove(self._current_location)
112 if os.path.join(self._current_location, 'pyautolib') in sys.path:
113 sys.path.remove(os.path.join(self._current_location, 'pyautolib'))
114
115 def _Install(self):
116 """Helper method that installs Chrome and creates a PyUITest object."""
117 self._pyauto = None
118 installer_path = next(self._installer_iter, None)
119 if not installer_path:
120 raise RuntimeError('No more builds left to install.')
121 ret = chrome_installer.Install(installer_path, self._install_type,
122 self._current_build, self._options)
123 if not ret:
124 raise RuntimeError('Chrome installation for build %s failed.' %
125 self._current_build)
126 try:
127 import pyauto
128 self._pyauto = pyauto.PyUITest(methodName='runTest',
129 browser_path=os.path.dirname(
130 self._installation.GetChromeExePath()))
131 self._pyauto.suite_holder = pyauto.PyUITestSuite(['test.py'])
132 self._pyauto.setUp()
133 except ImportError, err:
134 logging.log(logging.ERROR, err)
135 self._pyauto = None
136
137 def InstallBuild(self):
138 self._current_location = next(self._dir_iterator, None)
139 sys.path.insert(0, self._current_location)
140 sys.path.insert(1, os.path.join(self._current_location, 'pyautolib'))
141 self._Install()
142
143 def _Update(self):
144 """Helper method for updating Chrome."""
145 self._RemovePaths()
146 self._current_location = next(self._dir_iterator, None)
147 assert (self._current_location)
148 sys.path.insert(0, self._current_location)
149 sys.path.insert(1, os.path.join(self._current_location, 'pyautolib'))
150 build = next(self._build_iterator, None)
151 if not build:
152 raise RuntimeError('No more builds left to install. Following builds '
153 'have already been installed: %r' % self._builds)
154 self._current_build = build
155 self._Install()
156
157 def UpdateBuild(self):
158 """Installs the second Chrome build specified in the command args."""
159 if self._pyauto:
160 self._pyauto.TearDown()
161 self._Refresh()
162 self._Update()
163
164 def _SrcFilesExist(self, root, items):
165 """Checks if specified files/folders exist at specified 'root' folder.
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(map(lambda p: os.path.exists(p) and True or False,
175 [os.path.join(root, path) for path in items]))
176
177 def _CheckoutSourceFiles(self, build, location):
178 """Checks out source files associated with the current build.
179
180 Args:
181 build: Chrome release version number.
182 location: Destination where source files will be saved.
183 Returns:
184 Zero if successful, otherwise a negative value.
185 """
186 try:
187 chrome_checkout.CheckOut(build, location)
188 return True
189 except AssertionError:
190 return False
191
192 def _FetchPrebuiltPyauto(self, url, location):
193 """Fetches the specified Chrome build.
194
195 Args:
196 url: URL where the build is located.
197 location: Location where the build will be downloaded.
198
199 Returns:
200 True if successful, otherwise False.
201 """
202 fetch_build = FetchPrebuilt(url, location, self._platform)
203 if fetch_build.DoesUrlExist(url):
204 return fetch_build.Run() == 0
205 else:
206 return False
207
208 def _DownloadInstaller(self, url, location):
209 """Downloads the Chrome installer.
210
211 Args:
212 url: URL where the installer is located.
213 location: Location where installer will be downloaded.
214
215 Returns:
216 True if successful, otherwise False.
217 """
218 try:
219 self._Download(self._installer_name, url, location)
220 return True
221 except (IOError, RuntimeError):
222 return False
223
224 def _DownloadDeps(self, build):
225 """Downloads Chrome build, source files, and Chrome installer.
226
227 Args:
228 build: Chrome release build number.
229
230 Returns:
231 True if successful, otherwise False.
232 """
233 url = '%s%s/%s' % (self._url, build, self._platform)
234 location = os.path.join('%s', '%s%s') % (tempfile.gettempdir(),
235 self._dir_prefix, build)
236 if os.path.isdir(location):
237 self._download_dirs.append(location)
238 self._installers.append(os.path.join(location, self._installer_name))
239 return True
240 else:
241 # Make temp dir. and download/checkout everything there first.
242 tmpdir = tempfile.mkdtemp()
243 # Check out Chrome source files.
244 if self._CheckoutSourceFiles(build, tmpdir):
245 # Fetch the Chrome build.
246 if self._FetchPrebuiltPyauto(url, tmpdir):
247 # Download the Chrome installer.
248 if self._DownloadInstaller(url, tmpdir):
249 try:
250 # This is a workaround because rename was causing problems.
251 shutil.copytree(tmpdir, location)
252 # Callback is there because hidden svn files are read-only, so
253 # we need to change their permissions on the fly to delete them.
254 shutil.rmtree(tmpdir, onerror=self._OnError)
255 self._download_dirs.append(location)
256 self._installers.append(os.path.join(location,
257 self._installer_name))
258 return True
259 except(OSError, IOError), err:
260 return False
261 return False
262
263 def _OnError(self, func, path, exc_info):
264 """Callback for shutil.rmtree."""
265 if not os.access(path, os.W_OK):
266 os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
267 func(path)
268
269 def _DeleteBuild(self):
270 """Uninstalls Chrome."""
271 ret = self._installation.UninstallChrome()
272 if ret:
273 self._build_iterator = iter(self._builds)
274 self._dir_iterator = iter(self._download_dirs)
275 self._current_build = next(self._build_iterator, None)
276 self._current_location = next(self._dir_iterator, None)
277 self._RemovePaths()
278 return ret
279
280 def _DeleteDepFiles(self):
281 """Deletes Chrome related files that were downloaded for testing."""
282 for path in self._download_dirs:
283 try:
284 shutil.rmtree(path, onerror=self._OnError)
285 except shutil.Error, err:
286 logging.log(logging.ERROR, err)
287 return -1
288 return 0
289
290 def _Download(self, dfile, url, dest=None):
291 """Downloads a file from the specified URL.
292
293 Args:
294 dfile: Name of the file to download.
295 url: URL where the file is located.
296 dest: Location where file will be downloaded. Default is CWD.
297
298 Returns:
299 Zero if successful, otherwise a non-zero value.
300 """
301 filename = ((dest and os.path.exists(dest)) and os.path.join(dest, dfile)
302 or os.path.join(tempfile.gettempdir(), dfile))
303 file_url = '%s/%s' % (url, dfile)
304 if not self._DoesUrlExist(file_url):
305 raise RuntimeError('Either the URL or the file name is invalid.')
306 try:
307 d = urllib.urlretrieve(file_url, filename)
308 except IOError, err:
309 raise err
310 return os.path.isfile(d[0])
311
312 def _DoesUrlExist(self, url):
313 """Checks if a URL exists.
314
315 Args:
316 url: URL to be verified.
317
318 Returns:
319 True if the URL exists, otherwise False.
320 """
321 parse = urlparse.urlparse(url)
322 if parse[0] == '' or parse[1] == '':
323 return False
324 try:
325 connection = httplib.HTTPConnection(parse.netloc)
326 connection.request('HEAD', parse.path)
327 response = connection.getresponse()
328 except (socket.error, socket.gaierror):
329 return False
330 finally:
331 connection.close()
332 if response.status == 302 or response.status == 301:
333 return self._DoesUrlExist(response.getheader('location'))
334 return response.status == 200
335
336
337 class Main(object):
338 """Main program for running Updater tests."""
339
340 _tests_file = 'PYAUTO_TESTS'
341 _mod_path = sys.argv[0]
342 _pyauto_doc_url = 'http://dev.chromium.org/developers/testing/pyauto'
343
344 def __init__(self):
345 self._ParseArgs()
346 self._Run()
347
348 def _GetUnitTests(self):
349 """Returns a list of unittests from the calling script."""
350 mod_name = [os.path.splitext(os.path.basename(self._mod_path))[0]]
351 if os.path.dirname(self._mod_path) not in sys.path:
352 sys.path.append(os.path.dirname(self._mod_path))
353 return unittest.defaultTestLoader.loadTestsFromNames(mod_name)
354
355 def _Run(self):
356 """Runs the unit tests."""
357 tests = self._GetUnitTests()
358 result = GTestTextTestRunner(verbosity=1).run(tests)
359 del(tests)
360 if not result.wasSuccessful():
361 print >>sys.stderr, ('Tests can be disabled by editing %s. Ref: %s'
362 % (self._tests_file, self._pyauto_doc_url))
363 sys.exit(1)
364 else:
365 sys.exit(0)
366
367 def _ParseArgs(self):
368 """Parses command line arguments."""
369 global _OPTIONS
370 parser = optparse.OptionParser()
371 parser.add_option(
372 '-b', '--builds', type='string', default='', dest='builds',
373 help='Specifies the two (or more) builds needed for testing.')
374 parser.add_option(
375 '-u', '--url', type='string', default='', dest='url',
376 help='Specifies the chrome-master2 url, without the build number.')
377 parser.add_option(
378 '-d', '--dir', type='string', default=os.getcwd(),
379 help='Specifies directory where the installer will be downloaded.')
380 parser.add_option(
381 '-o', '--options', type='string', default='',
382 help='Specifies any additional Chrome options (i.e. --system-level).')
383 opts, args = parser.parse_args()
384 _OPTIONS = opts
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698