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

Side by Side Diff: chrome/test/mini_installer/test_installer.py

Issue 2747023002: Cleanup machine based on the state in configuration file for mini installer test.
Patch Set: refactor to use visitor pattern Created 3 years, 9 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
OLDNEW
1 # Copyright 2013 The Chromium Authors. All rights reserved. 1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """This script tests the installer with test cases specified in the config file. 5 """This script tests the installer with test cases specified in the config file.
6 6
7 For each test case, it checks that the machine states after the execution of 7 For each test case, it checks that the machine states after the execution of
8 each command match the expected machine states. For more details, take a look at 8 each command match the expected machine states. For more details, take a look at
9 the design documentation at http://goo.gl/Q0rGM6 9 the design documentation at http://goo.gl/Q0rGM6
10 """ 10 """
11 11
12 import argparse 12 import argparse
13 import datetime 13 import datetime
14 import inspect 14 import inspect
15 import json 15 import json
16 import os 16 import os
17 import subprocess 17 import subprocess
18 import sys 18 import sys
19 import time 19 import time
20 import traceback 20 import traceback
21 import unittest 21 import unittest
22 import _winreg
23 22
23 from state_walker import StateWalker
24 from variable_expander import VariableExpander 24 from variable_expander import VariableExpander
25 import verifier_runner 25
26 import cleaner_visitor
27 import verifier_visitor
26 28
27 29
28 def LogMessage(message): 30 def LogMessage(message):
29 """Logs a message to stderr. 31 """Logs a message to stderr.
30 32
31 Args: 33 Args:
32 message: The message string to be logged. 34 message: The message string to be logged.
33 """ 35 """
34 now = datetime.datetime.now() 36 now = datetime.datetime.now()
35 frameinfo = inspect.getframeinfo(inspect.currentframe().f_back) 37 frameinfo = inspect.getframeinfo(inspect.currentframe().f_back)
(...skipping 15 matching lines...) Expand all
51 """ 53 """
52 def __init__(self): 54 def __init__(self):
53 self.states = {} 55 self.states = {}
54 self.actions = {} 56 self.actions = {}
55 self.tests = [] 57 self.tests = []
56 58
57 59
58 class InstallerTest(unittest.TestCase): 60 class InstallerTest(unittest.TestCase):
59 """Tests a test case in the config file.""" 61 """Tests a test case in the config file."""
60 62
61 def __init__(self, name, test, config, variable_expander, quiet): 63 def __init__(self, name, test, config, variable_expander, quiet, clean_state):
62 """Constructor. 64 """Constructor.
63 65
64 Args: 66 Args:
65 name: The name of this test. 67 name: The name of this test.
66 test: An array of alternating state names and action names, starting and 68 test: An array of alternating state names and action names, starting and
67 ending with state names. 69 ending with state names.
68 config: The Config object. 70 config: The Config object.
69 variable_expander: A VariableExpander object. 71 variable_expander: A VariableExpander object.
72 quiet: A boolean to control the test output.
73 clean_state: A string to represent the cleanup state.
70 """ 74 """
71 super(InstallerTest, self).__init__() 75 super(InstallerTest, self).__init__()
72 self._name = name 76 self._name = name
73 self._test = test 77 self._test = test
74 self._config = config 78 self._config = config
75 self._variable_expander = variable_expander 79 self._variable_expander = variable_expander
76 self._quiet = quiet 80 self._quiet = quiet
77 self._verifier_runner = verifier_runner.VerifierRunner() 81 self._verifier = StateWalker(verifier_visitor.VerifierVisitor())
78 self._clean_on_teardown = True 82 self._clean_on_teardown = True
83 self._clean_state = clean_state
79 84
80 def __str__(self): 85 def __str__(self):
81 """Returns a string representing the test case. 86 """Returns a string representing the test case.
82 87
83 Returns: 88 Returns:
84 A string created by joining state names and action names together with 89 A string created by joining state names and action names together with
85 ' -> ', for example, 'Test: clean -> install chrome -> chrome_installed'. 90 ' -> ', for example, 'Test: clean -> install chrome -> chrome_installed'.
86 """ 91 """
87 return '%s: %s\n' % (self._name, ' -> '.join(self._test)) 92 return '%s: %s\n' % (self._name, ' -> '.join(self._test))
88 93
(...skipping 26 matching lines...) Expand all
115 state = self._test[i + 1] 120 state = self._test[i + 1]
116 self._VerifyState(state) 121 self._VerifyState(state)
117 122
118 # If the test makes it here, it means it was successful, because RunCommand 123 # If the test makes it here, it means it was successful, because RunCommand
119 # and _VerifyState throw an exception on failure. 124 # and _VerifyState throw an exception on failure.
120 self._clean_on_teardown = False 125 self._clean_on_teardown = False
121 126
122 def tearDown(self): 127 def tearDown(self):
123 """Cleans up the machine if the test case fails.""" 128 """Cleans up the machine if the test case fails."""
124 if self._clean_on_teardown: 129 if self._clean_on_teardown:
125 RunCleanCommand(True, self._variable_expander) 130 RunCleanCommand(True, self._config.states[self._clean_state],
131 self._variable_expander)
126 132
127 def shortDescription(self): 133 def shortDescription(self):
128 """Overridden from unittest.TestCase. 134 """Overridden from unittest.TestCase.
129 135
130 We return None as the short description to suppress its printing. 136 We return None as the short description to suppress its printing.
131 The default implementation of this method returns the docstring of the 137 The default implementation of this method returns the docstring of the
132 runTest method, which is not useful since it's the same for every test case. 138 runTest method, which is not useful since it's the same for every test case.
133 The description from the __str__ method is informative enough. 139 The description from the __str__ method is informative enough.
134 """ 140 """
135 return None 141 return None
136 142
137 def _VerifyState(self, state): 143 def _VerifyState(self, state):
138 """Verifies that the current machine state matches a given state. 144 """Verifies that the current machine state matches a given state.
139 145
140 Args: 146 Args:
141 state: A state name. 147 state: A state name.
142 """ 148 """
143 if not self._quiet: 149 if not self._quiet:
144 LogMessage('Verifying state %s' % state) 150 LogMessage('Verifying state %s' % state)
145 try: 151 try:
146 self._verifier_runner.VerifyAll(self._config.states[state], 152 self._verifier.Walk(self._variable_expander, self._config.states[state])
147 self._variable_expander)
148 except AssertionError as e: 153 except AssertionError as e:
149 # If an AssertionError occurs, we intercept it and add the state name 154 # If an AssertionError occurs, we intercept it and add the state name
150 # to the error message so that we know where the test fails. 155 # to the error message so that we know where the test fails.
151 raise AssertionError("In state '%s', %s" % (state, e)) 156 raise AssertionError("In state '%s', %s" % (state, e))
152 157
153 158
154 def RunCommand(command, variable_expander): 159 def RunCommand(command, variable_expander):
155 """Runs the given command from the current file's directory. 160 """Runs the given command from the current file's directory.
156 161
157 This function throws an Exception if the command returns with non-zero exit 162 This function throws an Exception if the command returns with non-zero exit
158 status. 163 status.
159 164
160 Args: 165 Args:
161 command: A command to run. It is expanded using Expand. 166 command: A command to run. It is expanded using Expand.
162 variable_expander: A VariableExpander object. 167 variable_expander: A VariableExpander object.
163 """ 168 """
164 expanded_command = variable_expander.Expand(command) 169 expanded_command = variable_expander.Expand(command)
165 script_dir = os.path.dirname(os.path.abspath(__file__)) 170 script_dir = os.path.dirname(os.path.abspath(__file__))
166 exit_status = subprocess.call(expanded_command, shell=True, cwd=script_dir) 171 exit_status = subprocess.call(expanded_command, shell=True, cwd=script_dir)
167 if exit_status != 0: 172 if exit_status != 0:
168 raise Exception('Command %s returned non-zero exit status %s' % ( 173 raise Exception('Command %s returned non-zero exit status %s' % (
169 expanded_command, exit_status)) 174 expanded_command, exit_status))
170 175
171 176 def RunCleanCommand(force_clean, clean_state, variable_expander):
172 def DeleteGoogleUpdateRegistration(system_level, registry_subkey,
173 variable_expander):
174 """Deletes Chrome's registration with Google Update.
175
176 Args:
177 system_level: True if system-level Chrome is to be deleted.
178 registry_subkey: The pre-expansion registry subkey for the product.
179 variable_expander: A VariableExpander object.
180 """
181 root = (_winreg.HKEY_LOCAL_MACHINE if system_level
182 else _winreg.HKEY_CURRENT_USER)
183 key_name = variable_expander.Expand(registry_subkey)
184 try:
185 key_handle = _winreg.OpenKey(root, key_name, 0,
186 _winreg.KEY_SET_VALUE |
187 _winreg.KEY_WOW64_32KEY)
188 _winreg.DeleteValue(key_handle, 'pv')
189 except WindowsError:
190 # The key isn't present, so there is no value to delete.
191 pass
192
193
194 def RunCleanCommand(force_clean, variable_expander):
195 """Puts the machine in the clean state (i.e. Chrome not installed). 177 """Puts the machine in the clean state (i.e. Chrome not installed).
196 178
197 Args: 179 Args:
198 force_clean: A boolean indicating whether to force cleaning existing 180 force_clean: A boolean indicating whether to force cleaning existing
199 installations. 181 installations.
200 variable_expander: A VariableExpander object. 182 variable_expander: A VariableExpander object.
201 """ 183 """
202 # A list of (system_level, product_name, product_switch, registry_subkey) 184 # A list of (product_name, product_switch)
203 # tuples for the possible installed products. 185 # tuples for the possible installed products.
204 data = [ 186 data = [
205 (False, '$CHROME_LONG_NAME', '', 187 ('$CHROME_LONG_NAME', ''),
206 '$CHROME_UPDATE_REGISTRY_SUBKEY'), 188 ('$CHROME_LONG_NAME', '--system-level'),
207 (True, '$CHROME_LONG_NAME', '--system-level',
208 '$CHROME_UPDATE_REGISTRY_SUBKEY'),
209 ] 189 ]
210 if variable_expander.Expand('$SUPPORTS_SXS') == 'True': 190 if variable_expander.Expand('$SUPPORTS_SXS') == 'True':
211 data.append((False, '$CHROME_LONG_NAME_SXS', '', 191 data.append(('$CHROME_LONG_NAME_SXS', ''))
212 '$CHROME_UPDATE_REGISTRY_SUBKEY_SXS'))
213 192
214 interactive_option = '--interactive' if not force_clean else '' 193 interactive_option = '--interactive' if not force_clean else ''
215 for system_level, product_name, product_switch, registry_subkey in data: 194 for product_name, product_switch in data:
216 command = ('python uninstall_chrome.py ' 195 command = ('python uninstall_chrome.py '
217 '--chrome-long-name="%s" ' 196 '--chrome-long-name="%s" '
218 '--no-error-if-absent %s %s' % 197 '--no-error-if-absent %s %s' %
219 (product_name, product_switch, interactive_option)) 198 (product_name, product_switch, interactive_option))
220 try: 199 try:
221 RunCommand(command, variable_expander) 200 RunCommand(command, variable_expander)
222 except: 201 except (Exception, OSError, ValueError):
223 message = traceback.format_exception(*sys.exc_info()) 202 message = traceback.format_exception(*sys.exc_info())
224 message.insert(0, 'Error cleaning up an old install with:\n') 203 message.insert(0, 'Error cleaning up an old install with:\n')
225 LogMessage(''.join(message)) 204 LogMessage(''.join(message))
226 if force_clean:
227 DeleteGoogleUpdateRegistration(system_level, registry_subkey,
228 variable_expander)
229 205
206 if force_clean:
207 StateWalker(cleaner_visitor.CleanerVisitor()).Walk(
208 variable_expander, clean_state)
230 209
231 def MergePropertyDictionaries(current_property, new_property): 210 def MergePropertyDictionaries(current_property, new_property):
232 """Merges the new property dictionary into the current property dictionary. 211 """Merges the new property dictionary into the current property dictionary.
233 212
234 This is different from general dictionary merging in that, in case there are 213 This is different from general dictionary merging in that, in case there are
235 keys with the same name, we merge values together in the first level, and we 214 keys with the same name, we merge values together in the first level, and we
236 override earlier values in the second level. For more details, take a look at 215 override earlier values in the second level. For more details, take a look at
237 http://goo.gl/uE0RoR 216 http://goo.gl/uE0RoR
238 217
239 Args: 218 Args:
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
316 variable_expander), 295 variable_expander),
317 config.tests) 296 config.tests)
318 for state_name, state_property_filenames in config_data['states']: 297 for state_name, state_property_filenames in config_data['states']:
319 config.states[state_name] = ParsePropertyFiles(directory, 298 config.states[state_name] = ParsePropertyFiles(directory,
320 state_property_filenames, 299 state_property_filenames,
321 variable_expander) 300 variable_expander)
322 for action_name, action_command in config_data['actions']: 301 for action_name, action_command in config_data['actions']:
323 config.actions[action_name] = action_command 302 config.actions[action_name] = action_command
324 return config 303 return config
325 304
305 DEFAULT_CLEAN_STATE = 'clean'
326 306
327 def main(): 307 def main():
328 parser = argparse.ArgumentParser() 308 parser = argparse.ArgumentParser()
329 parser.add_argument('--build-dir', default='out', 309 parser.add_argument('--build-dir', default='out',
330 help='Path to main build directory (the parent of the ' 310 help='Path to main build directory (the parent of the '
331 'Release or Debug directory)') 311 'Release or Debug directory)')
332 parser.add_argument('--target', default='Release', 312 parser.add_argument('--target', default='Release',
333 help='Build target (Release or Debug)') 313 help='Build target (Release or Debug)')
334 parser.add_argument('--force-clean', action='store_true', default=False, 314 parser.add_argument('--force-clean', action='store_true', default=False,
335 help='Force cleaning existing installations') 315 help='Force cleaning existing installations')
316 parser.add_argument('--clean-state', default='clean',
317 help='The state that is used to cleanup the machine after'
318 'each test case.')
336 parser.add_argument('-q', '--quiet', action='store_true', default=False, 319 parser.add_argument('-q', '--quiet', action='store_true', default=False,
337 help='Reduce test runner output') 320 help='Reduce test runner output')
338 parser.add_argument('--write-full-results-to', metavar='FILENAME', 321 parser.add_argument('--write-full-results-to', metavar='FILENAME',
339 help='Path to write the list of full results to.') 322 help='Path to write the list of full results to.')
340 parser.add_argument('--config', metavar='FILENAME', 323 parser.add_argument('--config', metavar='FILENAME',
341 help='Path to test configuration file') 324 help='Path to test configuration file')
342 parser.add_argument('test', nargs='*', 325 parser.add_argument('test', nargs='*',
343 help='Name(s) of tests to run.') 326 help=('Name(s) of tests to run. For example, '
327 '__main__.InstallerTest.ChromeUserLevel'))
344 args = parser.parse_args() 328 args = parser.parse_args()
345 if not args.config: 329 if not args.config:
346 parser.error('missing mandatory --config FILENAME argument') 330 parser.error('missing mandatory --config FILENAME argument')
347 331
348 mini_installer_path = os.path.join(args.build_dir, args.target, 332 mini_installer_path = os.path.join(args.build_dir, args.target,
349 'mini_installer.exe') 333 'mini_installer.exe')
350 assert os.path.exists(mini_installer_path), ('Could not find file %s' % 334 assert os.path.exists(mini_installer_path), ('Could not find file %s' %
351 mini_installer_path) 335 mini_installer_path)
352 336
353 next_version_mini_installer_path = os.path.join( 337 next_version_mini_installer_path = os.path.join(
354 args.build_dir, args.target, 'next_version_mini_installer.exe') 338 args.build_dir, args.target, 'next_version_mini_installer.exe')
355 assert os.path.exists(next_version_mini_installer_path), ( 339 assert os.path.exists(next_version_mini_installer_path), (
356 'Could not find file %s' % next_version_mini_installer_path) 340 'Could not find file %s' % next_version_mini_installer_path)
357 341
358 suite = unittest.TestSuite() 342 suite = unittest.TestSuite()
359 343
360 variable_expander = VariableExpander(mini_installer_path, 344 variable_expander = VariableExpander(mini_installer_path,
361 next_version_mini_installer_path) 345 next_version_mini_installer_path)
362 config = ParseConfigFile(args.config, variable_expander) 346 config = ParseConfigFile(args.config, variable_expander)
363 347
364 RunCleanCommand(args.force_clean, variable_expander) 348 RunCleanCommand(args.force_clean, config.states[args.clean_state],
349 variable_expander)
365 for test in config.tests: 350 for test in config.tests:
366 # If tests were specified via |tests|, their names are formatted like so: 351 # If tests were specified via |tests|, their names are formatted like so:
367 test_name = '%s.%s.%s' % (InstallerTest.__module__, 352 test_name = '%s.%s.%s' % (InstallerTest.__module__,
368 InstallerTest.__name__, 353 InstallerTest.__name__,
369 test['name']) 354 test['name'])
370 if not args.test or test_name in args.test: 355 if not args.test or test_name in args.test:
371 suite.addTest(InstallerTest(test['name'], test['traversal'], config, 356 suite.addTest(InstallerTest(test['name'], test['traversal'], config,
372 variable_expander, args.quiet)) 357 variable_expander, args.quiet,
358 args.clean_state))
373 359
374 verbosity = 2 if not args.quiet else 1 360 verbosity = 2 if not args.quiet else 1
375 result = unittest.TextTestRunner(verbosity=verbosity).run(suite) 361 result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
376 if args.write_full_results_to: 362 if args.write_full_results_to:
377 with open(args.write_full_results_to, 'w') as fp: 363 with open(args.write_full_results_to, 'w') as fp:
378 json.dump(_FullResults(suite, result, {}), fp, indent=2) 364 json.dump(_FullResults(suite, result, {}), fp, indent=2)
379 fp.write('\n') 365 fp.write('\n')
380 return 0 if result.wasSuccessful() else 1 366 return 0 if result.wasSuccessful() else 1
381 367
382 368
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
442 trie[path] = value 428 trie[path] = value
443 return 429 return
444 directory, rest = path.split(TEST_SEPARATOR, 1) 430 directory, rest = path.split(TEST_SEPARATOR, 1)
445 if directory not in trie: 431 if directory not in trie:
446 trie[directory] = {} 432 trie[directory] = {}
447 _AddPathToTrie(trie[directory], rest, value) 433 _AddPathToTrie(trie[directory], rest, value)
448 434
449 435
450 if __name__ == '__main__': 436 if __name__ == '__main__':
451 sys.exit(main()) 437 sys.exit(main())
OLDNEW
« no previous file with comments | « chrome/test/mini_installer/state_walker.py ('k') | chrome/test/mini_installer/variable_expander.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698