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

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.
Nirnimesh 2012/08/09 21:32:48 docstring. Give an example test. Look at the top
nkang 2012/08/16 23:46:24 Added a docstring and included instructions on how
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 chrome_installer import ChromeInstallation
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 from pyauto_utils import GTestTextTestRunner
28
29 _OPTIONS = None
Nirnimesh 2012/08/09 21:32:48 Do not use globals. What you're doing can easily b
nkang 2012/08/16 23:46:24 I actually did it this way because I was trying to
Nirnimesh 2012/08/22 07:06:34 globals should not be used unless absolutely neces
nkang 2012/08/24 22:45:26 Per our conversation, I created a 'staticmethod' i
30
31
32 class InstallTest(unittest.TestCase):
33 """Test fixture for tests involving installing/updating Chrome.
34
35 Provides an interface to install or update chrome from within a testcase, and
36 allows users to run pyauto tests using the installed version. User and system
37 level installations are supported, and either one can be used for running the
38 pyauto tests. Pyautolib files are downloaded at runtime and a PyUITest object
39 is created when Chrome is installed or updated. Users can utilize that object
40 to run updater tests. All Updater tests should derive from this class.
41 """
42
43 _build_iterator = None
44 _current_build = ''
45 _current_location = ''
46 _dir_prefix = '__CHRBLD__'
Nirnimesh 2012/08/09 21:32:48 why the mystique name?
nkang 2012/08/16 23:46:24 We have a particular naming scheme for folders whe
Nirnimesh 2012/08/22 07:06:34 Declare this convention somewhere.
47 _dir_iterator = None
48 _installation = None
49 _installer_name = 'mini_installer.exe'
Nirnimesh 2012/08/09 21:32:48 So all this is win only? Where did you declare/che
nkang 2012/08/16 23:46:24 Updated the docstring so it states that only Windo
50 _pyauto = None
51 _installers = []
52 _download_dirs = []
53
54 def __init__(self, methodName='runTest'):
55 unittest.TestCase.__init__(self, methodName)
56 self._platform = self._GetPlatform()
57 self._Initialize()
58 current_build = ChromeInstallation.GetCurrent()
59 if current_build:
60 current_build.Uninstall()
61 for build in self._builds:
62 if not self._DownloadDeps(build):
Nirnimesh 2012/08/09 21:32:48 Do you want the downloading to be done separately
nkang 2012/08/16 23:46:24 No, the downloading will occur only once. This is
63 raise RuntimeError('Could not download dependencies.')
64 self._installer_iter = iter(self._installers)
65 self._dir_iterator = iter(self._download_dirs)
66
67 def _Initialize(self):
68 """Sets test parameters."""
69 global _OPTIONS
70 self._url = _OPTIONS.url
71 self._builds = _OPTIONS.builds and _OPTIONS.builds.split(',') or []
72 if not self._url or not self._builds:
73 raise RuntimeError('Please specify a valid URL and two Chrome builds.')
74 self._builds.sort()
75 self._url = self._url.endswith('/') and self._url or self._url + '/'
76 self._dir = os.path.isdir(_OPTIONS.dir) and _OPTIONS.dir or os.getcwd()
77 self._options = (_OPTIONS.options and _OPTIONS.options.replace(',', ' ')
78 or '')
79 self._install_type = ('system-level' in self._options and
80 chrome_installer.InstallationType.SYSTEM or
81 chrome_installer.InstallationType.USER)
82 self._build_iterator = iter(self._builds)
83 self._current_build = next(self._build_iterator, None)
84
85 def setUp(self):
86 """Called before each unittest to prepare the test fixture."""
87 self.InstallBuild()
88 self.failIf(self._pyauto == None)
Nirnimesh 2012/08/09 21:32:48 call parent's setup
nkang 2012/08/16 23:46:24 Sorry, I didn't fully understand this comment. The
Nirnimesh 2012/08/22 07:06:34 The fact that the parent does nothing is irrelevan
89
90 def tearDown(self):
91 """Called at the end of each unittest to do any test related cleanup."""
92 self._Refresh()
93 self._DeleteBuild()
Nirnimesh 2012/08/09 21:32:48 call parent's tearDown
nkang 2012/08/16 23:46:24 The parent class of InstallTest is unittest.TestCa
94
95 def _GetPlatform(self):
96 """Returns the platform name."""
97 return ({'Windows': 'win',
98 'Darwin': 'mac',
99 'Linux': 'linux'}).get(platform.system())
100
101 def _Refresh(self):
Nirnimesh 2012/08/09 21:32:48 Refresh? It's not clear from the method name that
nkang 2012/08/16 23:46:24 Renamed to _ClearModulesRegistry.
102 """Deletes the PyUITest object and clears the modules registry."""
103 if self._pyauto:
104 del(self._pyauto)
Nirnimesh 2012/08/09 21:32:48 remove parens
nkang 2012/08/16 23:46:24 Done.
105 for module in ['pyauto', 'pyautolib', '_pyautolib']:
106 if module in sys.modules:
107 sys.modules.pop(module)
108
109 def _RemovePaths(self):
110 """Restores the sys.path variable to its original state."""
111 sys.path = list(frozenset(sys.path))
112 if self._current_location:
113 if self._current_location in sys.path:
114 sys.path.remove(self._current_location)
115 if os.path.join(self._current_location, 'pyautolib') in sys.path:
116 sys.path.remove(os.path.join(self._current_location, 'pyautolib'))
117
118 def _Install(self):
119 """Helper method that installs Chrome and creates a PyUITest object."""
120 self._pyauto = None
121 installer_path = next(self._installer_iter, None)
122 if not installer_path:
123 raise RuntimeError('No more builds left to install.')
124 self._installation = chrome_installer.Install(installer_path,
125 self._install_type,
126 self._current_build,
127 self._options)
128 try:
129 import pyauto
130 self._pyauto = pyauto.PyUITest(methodName='runTest',
131 browser_path=os.path.dirname(
132 self._installation.GetExePath()))
133 self._pyauto.suite_holder = pyauto.PyUITestSuite(['test.py'])
Nirnimesh 2012/08/09 21:32:48 [] would do I think
nkang 2012/08/16 23:46:24 Yes, it did. Got rid of the arbitrary file name.
134 self._pyauto.setUp()
135 except ImportError, err:
136 logging.log(logging.ERROR, err)
137 self._pyauto = None
138
139 def InstallBuild(self):
140 self._current_location = next(self._dir_iterator, None)
141 sys.path.insert(0, self._current_location)
142 sys.path.insert(1, os.path.join(self._current_location, 'pyautolib'))
143 self._Install()
144
145 def _Update(self):
146 """Helper method for updating Chrome."""
147 self._RemovePaths()
148 self._current_location = next(self._dir_iterator, None)
149 assert (self._current_location)
150 sys.path.insert(0, self._current_location)
151 sys.path.insert(1, os.path.join(self._current_location, 'pyautolib'))
152 build = next(self._build_iterator, None)
153 if not build:
154 raise RuntimeError('No more builds left to install. Following builds '
155 'have already been installed: %r' % self._builds)
156 self._current_build = build
157 self._Install()
158
159 def UpdateBuild(self):
160 """Installs the second Chrome build specified in the command args."""
161 if self._pyauto:
162 self._pyauto.TearDown()
163 self._Refresh()
164 self._Update()
165
166 def _SrcFilesExist(self, root, items):
167 """Checks if specified files/folders exist at specified 'root' folder.
168
169 Args:
170 root: Parent folder where all the source directories reside.
171 items: List of files/folders to be verified for existence in the root.
172
173 Returns:
174 True, if all sub-folders exist in the root, otherwise False.
175 """
176 return all(map(lambda p: os.path.exists(p) and True or False,
177 [os.path.join(root, path) for path in items]))
178
179 def _CheckoutSourceFiles(self, build, location):
180 """Checks out source files associated with the current build.
181
182 Args:
183 build: Chrome release version number.
184 location: Destination where source files will be saved.
185 Returns:
186 Zero if successful, otherwise a negative value.
187 """
188 try:
189 chrome_checkout.CheckOut(build, location)
190 return True
191 except AssertionError:
192 return False
193
194 def _FetchPrebuiltPyauto(self, url, location):
195 """Fetches the specified Chrome build.
196
197 Args:
198 url: URL where the build is located.
199 location: Location where the build will be downloaded.
200
201 Returns:
202 True if successful, otherwise False.
203 """
204 fetch_build = FetchPrebuilt(url, location, self._platform)
205 if fetch_build.DoesUrlExist(url):
206 return fetch_build.Run() == 0
207 else:
208 return False
209
210 def _DownloadInstaller(self, url, location):
211 """Downloads the Chrome installer.
212
213 Args:
214 url: URL where the installer is located.
215 location: Location where installer will be downloaded.
216
217 Returns:
218 True if successful, otherwise False.
219 """
220 try:
221 self._Download(self._installer_name, url, location)
222 return True
223 except (IOError, RuntimeError):
224 return False
225
226 def _DownloadDeps(self, build):
227 """Downloads Chrome build, source files, and Chrome installer.
228
229 Args:
230 build: Chrome release build number.
231
232 Returns:
233 True if successful, otherwise False.
234 """
235 url = '%s%s/%s' % (self._url, build, self._platform)
236 location = os.path.join('%s', '%s%s') % (tempfile.gettempdir(),
237 self._dir_prefix, build)
238 if os.path.isdir(location):
239 self._download_dirs.append(location)
240 self._installers.append(os.path.join(location, self._installer_name))
241 return True
242 else:
243 tmpdir = tempfile.mkdtemp()
244 if self._CheckoutSourceFiles(build, tmpdir):
245 if self._FetchPrebuiltPyauto(url, tmpdir):
246 if self._DownloadInstaller(url, tmpdir):
247 try:
248 # This is a workaround because rename was causing problems.
249 shutil.copytree(tmpdir, location)
250 # Callback is there because hidden svn files are read-only, so
251 # we need to change their permissions on the fly to delete them.
252 shutil.rmtree(tmpdir, onerror=self._OnError)
253 self._download_dirs.append(location)
254 self._installers.append(os.path.join(location,
255 self._installer_name))
256 return True
257 except(OSError, IOError), err:
258 return False
259 return False
260
261 def _OnError(self, func, path, exc_info):
Nirnimesh 2012/08/09 21:32:48 I've seen this elsewhere. Move this and line 252,
nkang 2012/08/16 23:46:24 Put line no. 252 in a helper method, and made the
262 """Callback for shutil.rmtree."""
263 if not os.access(path, os.W_OK):
264 os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
265 func(path)
266
267 def _DeleteBuild(self):
268 """Uninstalls Chrome."""
269 self._installation.Uninstall()
270 self._build_iterator = iter(self._builds)
271 self._dir_iterator = iter(self._download_dirs)
272 self._current_build = next(self._build_iterator, None)
273 self._current_location = next(self._dir_iterator, None)
274 self._RemovePaths()
275
276 def _DeleteDepFiles(self):
277 """Deletes Chrome related files that were downloaded for testing."""
278 for path in self._download_dirs:
279 try:
280 shutil.rmtree(path, onerror=self._OnError)
281 except shutil.Error, err:
282 logging.log(logging.ERROR, err)
283 return -1
284 return 0
285
286 def _Download(self, dfile, url, dest=None):
Nirnimesh 2012/08/09 21:32:48 I've seen similar function elsewhere. Reuse code.
nkang 2012/08/16 23:46:24 The only other method that's somewhat similar is c
287 """Downloads a file from the specified URL.
288
289 Args:
290 dfile: Name of the file to download.
291 url: URL where the file is located.
292 dest: Location where file will be downloaded. Default is CWD.
293
294 Returns:
295 Zero if successful, otherwise a non-zero value.
296 """
297 filename = ((dest and os.path.exists(dest)) and os.path.join(dest, dfile)
298 or os.path.join(tempfile.gettempdir(), dfile))
299 file_url = '%s/%s' % (url, dfile)
300 if not self._DoesUrlExist(file_url):
301 raise RuntimeError('Either the URL or the file name is invalid.')
302 try:
303 d = urllib.urlretrieve(file_url, filename)
304 except IOError, err:
305 raise err
306 return os.path.isfile(d[0])
307
308 def _DoesUrlExist(self, url):
309 """Checks if a URL exists.
310
311 Args:
312 url: URL to be verified.
313
314 Returns:
315 True if the URL exists, otherwise False.
316 """
317 parse = urlparse.urlparse(url)
318 if parse[0] == '' or parse[1] == '':
319 return False
320 try:
321 connection = httplib.HTTPConnection(parse.netloc)
322 connection.request('HEAD', parse.path)
323 response = connection.getresponse()
324 except (socket.error, socket.gaierror):
325 return False
326 finally:
327 connection.close()
328 if response.status == 302 or response.status == 301:
329 return self._DoesUrlExist(response.getheader('location'))
330 return response.status == 200
331
332
333 class Main(object):
334 """Main program for running Updater tests."""
335
336 _tests_file = 'PYAUTO_TESTS'
337 _mod_path = sys.argv[0]
338 _pyauto_doc_url = 'http://dev.chromium.org/developers/testing/pyauto'
339
340 def __init__(self):
341 self._ParseArgs()
342 self._Run()
343
344 def _GetUnitTests(self):
Nirnimesh 2012/08/09 21:32:48 remove 'Unit'
nkang 2012/08/16 23:46:24 Renamed to _GetTests.
345 """Returns a list of unittests from the calling script."""
346 mod_name = [os.path.splitext(os.path.basename(self._mod_path))[0]]
347 if os.path.dirname(self._mod_path) not in sys.path:
348 sys.path.append(os.path.dirname(self._mod_path))
349 return unittest.defaultTestLoader.loadTestsFromNames(mod_name)
350
351 def _Run(self):
352 """Runs the unit tests."""
353 tests = self._GetUnitTests()
354 result = GTestTextTestRunner(verbosity=1).run(tests)
355 del(tests)
356 if not result.wasSuccessful():
357 print >>sys.stderr, ('Tests can be disabled by editing %s. Ref: %s'
358 % (self._tests_file, self._pyauto_doc_url))
359 sys.exit(1)
360 else:
Nirnimesh 2012/08/09 21:32:48 remove the else part. it's redundant
nkang 2012/08/16 23:46:24 Removed else.
361 sys.exit(0)
362
363 def _ParseArgs(self):
Nirnimesh 2012/08/09 21:32:48 Move _ParseArgs before _Run
nkang 2012/08/16 23:46:24 Done.
364 """Parses command line arguments."""
365 global _OPTIONS
366 parser = optparse.OptionParser()
367 parser.add_option(
368 '-b', '--builds', type='string', default='', dest='builds',
369 help='Specifies the two (or more) builds needed for testing.')
370 parser.add_option(
371 '-u', '--url', type='string', default='', dest='url',
372 help='Specifies the chrome-master2 url, without the build number.')
Nirnimesh 2012/08/09 21:32:48 do not refer internal URLs/hostname.
nkang 2012/08/16 23:46:24 Removed the part that referred to an internal URL.
373 parser.add_option(
374 '-d', '--dir', type='string', default=os.getcwd(),
375 help='Specifies directory where the installer will be downloaded.')
376 parser.add_option(
377 '-o', '--options', type='string', default='',
378 help='Specifies any additional Chrome options (i.e. --system-level).')
379 opts, args = parser.parse_args()
380 _OPTIONS = opts
Nirnimesh 2012/08/09 21:32:48 verify the sanity of the args here, esp --builds
Nirnimesh 2012/08/09 21:32:48 Before firing off the install tests, it'd be nice
nkang 2012/08/16 23:46:24 Wrote a new method called ValidateArgs, which conf
nkang 2012/08/16 23:46:24 I am not entirely sure how we can confirm that the
Nirnimesh 2012/08/22 07:06:34 Yes, running a test from test_basic would be fine.
nkang 2012/08/24 22:45:26 After taking everything into consideration, I thin
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698