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

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

Powered by Google App Engine
This is Rietveld 408576698