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

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

Powered by Google App Engine
This is Rietveld 408576698