OLD | NEW |
---|---|
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 |
24 import cleanup | |
25 import verifier_runner | 25 import verifier_runner |
26 | 26 |
27 | 27 |
28 def LogMessage(message): | 28 def LogMessage(message): |
29 """Logs a message to stderr. | 29 """Logs a message to stderr. |
30 | 30 |
31 Args: | 31 Args: |
32 message: The message string to be logged. | 32 message: The message string to be logged. |
33 """ | 33 """ |
34 now = datetime.datetime.now() | 34 now = datetime.datetime.now() |
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
115 state = self._test[i + 1] | 115 state = self._test[i + 1] |
116 self._VerifyState(state) | 116 self._VerifyState(state) |
117 | 117 |
118 # If the test makes it here, it means it was successful, because RunCommand | 118 # If the test makes it here, it means it was successful, because RunCommand |
119 # and _VerifyState throw an exception on failure. | 119 # and _VerifyState throw an exception on failure. |
120 self._clean_on_teardown = False | 120 self._clean_on_teardown = False |
121 | 121 |
122 def tearDown(self): | 122 def tearDown(self): |
123 """Cleans up the machine if the test case fails.""" | 123 """Cleans up the machine if the test case fails.""" |
124 if self._clean_on_teardown: | 124 if self._clean_on_teardown: |
125 RunCleanCommand(True, self._variable_expander) | 125 RunCleanCommand(True, self._config.states[self._test[-1]], |
grt (UTC plus 2)
2017/03/14 09:19:44
rather than baking in the assumption that the last
zmin
2017/03/14 23:13:00
Done.
| |
126 self._variable_expander) | |
126 | 127 |
127 def shortDescription(self): | 128 def shortDescription(self): |
128 """Overridden from unittest.TestCase. | 129 """Overridden from unittest.TestCase. |
129 | 130 |
130 We return None as the short description to suppress its printing. | 131 We return None as the short description to suppress its printing. |
131 The default implementation of this method returns the docstring of the | 132 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. | 133 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. | 134 The description from the __str__ method is informative enough. |
134 """ | 135 """ |
135 return None | 136 return None |
(...skipping 25 matching lines...) Expand all Loading... | |
161 command: A command to run. It is expanded using Expand. | 162 command: A command to run. It is expanded using Expand. |
162 variable_expander: A VariableExpander object. | 163 variable_expander: A VariableExpander object. |
163 """ | 164 """ |
164 expanded_command = variable_expander.Expand(command) | 165 expanded_command = variable_expander.Expand(command) |
165 script_dir = os.path.dirname(os.path.abspath(__file__)) | 166 script_dir = os.path.dirname(os.path.abspath(__file__)) |
166 exit_status = subprocess.call(expanded_command, shell=True, cwd=script_dir) | 167 exit_status = subprocess.call(expanded_command, shell=True, cwd=script_dir) |
167 if exit_status != 0: | 168 if exit_status != 0: |
168 raise Exception('Command %s returned non-zero exit status %s' % ( | 169 raise Exception('Command %s returned non-zero exit status %s' % ( |
169 expanded_command, exit_status)) | 170 expanded_command, exit_status)) |
170 | 171 |
171 | 172 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). | 173 """Puts the machine in the clean state (i.e. Chrome not installed). |
196 | 174 |
197 Args: | 175 Args: |
198 force_clean: A boolean indicating whether to force cleaning existing | 176 force_clean: A boolean indicating whether to force cleaning existing |
199 installations. | 177 installations. |
200 variable_expander: A VariableExpander object. | 178 variable_expander: A VariableExpander object. |
201 """ | 179 """ |
202 # A list of (system_level, product_name, product_switch, registry_subkey) | 180 # A list of (product_name, product_switch) |
203 # tuples for the possible installed products. | 181 # tuples for the possible installed products. |
204 data = [ | 182 data = [ |
205 (False, '$CHROME_LONG_NAME', '', | 183 ('$CHROME_LONG_NAME', ''), |
206 '$CHROME_UPDATE_REGISTRY_SUBKEY'), | 184 ('$CHROME_LONG_NAME', '--system-level'), |
207 (True, '$CHROME_LONG_NAME', '--system-level', | |
208 '$CHROME_UPDATE_REGISTRY_SUBKEY'), | |
209 ] | 185 ] |
210 if variable_expander.Expand('$SUPPORTS_SXS') == 'True': | 186 if variable_expander.Expand('$SUPPORTS_SXS') == 'True': |
211 data.append((False, '$CHROME_LONG_NAME_SXS', '', | 187 data.append(('$CHROME_LONG_NAME_SXS', '')) |
212 '$CHROME_UPDATE_REGISTRY_SUBKEY_SXS')) | |
213 | 188 |
214 interactive_option = '--interactive' if not force_clean else '' | 189 interactive_option = '--interactive' if not force_clean else '' |
215 for system_level, product_name, product_switch, registry_subkey in data: | 190 for product_name, product_switch in data: |
216 command = ('python uninstall_chrome.py ' | 191 command = ('python uninstall_chrome.py ' |
217 '--chrome-long-name="%s" ' | 192 '--chrome-long-name="%s" ' |
218 '--no-error-if-absent %s %s' % | 193 '--no-error-if-absent %s %s' % |
219 (product_name, product_switch, interactive_option)) | 194 (product_name, product_switch, interactive_option)) |
220 try: | 195 try: |
221 RunCommand(command, variable_expander) | 196 RunCommand(command, variable_expander) |
222 except: | 197 except (Exception, OSError, ValueError): |
223 message = traceback.format_exception(*sys.exc_info()) | 198 message = traceback.format_exception(*sys.exc_info()) |
224 message.insert(0, 'Error cleaning up an old install with:\n') | 199 message.insert(0, 'Error cleaning up an old install with:\n') |
225 LogMessage(''.join(message)) | 200 LogMessage(''.join(message)) |
226 if force_clean: | 201 |
227 DeleteGoogleUpdateRegistration(system_level, registry_subkey, | 202 if force_clean: |
228 variable_expander) | 203 cleanup.CleanupRunner.Create().CleanAll(clean_state, variable_expander) |
229 | 204 |
230 | 205 |
231 def MergePropertyDictionaries(current_property, new_property): | 206 def MergePropertyDictionaries(current_property, new_property): |
232 """Merges the new property dictionary into the current property dictionary. | 207 """Merges the new property dictionary into the current property dictionary. |
233 | 208 |
234 This is different from general dictionary merging in that, in case there are | 209 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 | 210 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 | 211 override earlier values in the second level. For more details, take a look at |
237 http://goo.gl/uE0RoR | 212 http://goo.gl/uE0RoR |
238 | 213 |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
316 variable_expander), | 291 variable_expander), |
317 config.tests) | 292 config.tests) |
318 for state_name, state_property_filenames in config_data['states']: | 293 for state_name, state_property_filenames in config_data['states']: |
319 config.states[state_name] = ParsePropertyFiles(directory, | 294 config.states[state_name] = ParsePropertyFiles(directory, |
320 state_property_filenames, | 295 state_property_filenames, |
321 variable_expander) | 296 variable_expander) |
322 for action_name, action_command in config_data['actions']: | 297 for action_name, action_command in config_data['actions']: |
323 config.actions[action_name] = action_command | 298 config.actions[action_name] = action_command |
324 return config | 299 return config |
325 | 300 |
301 DEFAULT_CLEAN_STATE = 'clean' | |
grt (UTC plus 2)
2017/03/14 09:19:44
how about making this an argument for the parser w
| |
326 | 302 |
327 def main(): | 303 def main(): |
328 parser = argparse.ArgumentParser() | 304 parser = argparse.ArgumentParser() |
329 parser.add_argument('--build-dir', default='out', | 305 parser.add_argument('--build-dir', default='out', |
330 help='Path to main build directory (the parent of the ' | 306 help='Path to main build directory (the parent of the ' |
331 'Release or Debug directory)') | 307 'Release or Debug directory)') |
332 parser.add_argument('--target', default='Release', | 308 parser.add_argument('--target', default='Release', |
333 help='Build target (Release or Debug)') | 309 help='Build target (Release or Debug)') |
334 parser.add_argument('--force-clean', action='store_true', default=False, | 310 parser.add_argument('--force-clean', action='store_true', default=False, |
335 help='Force cleaning existing installations') | 311 help='Force cleaning existing installations') |
336 parser.add_argument('-q', '--quiet', action='store_true', default=False, | 312 parser.add_argument('-q', '--quiet', action='store_true', default=False, |
337 help='Reduce test runner output') | 313 help='Reduce test runner output') |
338 parser.add_argument('--write-full-results-to', metavar='FILENAME', | 314 parser.add_argument('--write-full-results-to', metavar='FILENAME', |
339 help='Path to write the list of full results to.') | 315 help='Path to write the list of full results to.') |
340 parser.add_argument('--config', metavar='FILENAME', | 316 parser.add_argument('--config', metavar='FILENAME', |
341 help='Path to test configuration file') | 317 help='Path to test configuration file') |
342 parser.add_argument('test', nargs='*', | 318 parser.add_argument('test', nargs='*', |
343 help='Name(s) of tests to run.') | 319 help=('Name(s) of tests to run. For example, ' |
320 '__main__.InstallerTest.ChromeUserLevel')) | |
344 args = parser.parse_args() | 321 args = parser.parse_args() |
345 if not args.config: | 322 if not args.config: |
346 parser.error('missing mandatory --config FILENAME argument') | 323 parser.error('missing mandatory --config FILENAME argument') |
347 | 324 |
348 mini_installer_path = os.path.join(args.build_dir, args.target, | 325 mini_installer_path = os.path.join(args.build_dir, args.target, |
349 'mini_installer.exe') | 326 'mini_installer.exe') |
350 assert os.path.exists(mini_installer_path), ('Could not find file %s' % | 327 assert os.path.exists(mini_installer_path), ('Could not find file %s' % |
351 mini_installer_path) | 328 mini_installer_path) |
352 | 329 |
353 next_version_mini_installer_path = os.path.join( | 330 next_version_mini_installer_path = os.path.join( |
354 args.build_dir, args.target, 'next_version_mini_installer.exe') | 331 args.build_dir, args.target, 'next_version_mini_installer.exe') |
355 assert os.path.exists(next_version_mini_installer_path), ( | 332 assert os.path.exists(next_version_mini_installer_path), ( |
356 'Could not find file %s' % next_version_mini_installer_path) | 333 'Could not find file %s' % next_version_mini_installer_path) |
357 | 334 |
358 suite = unittest.TestSuite() | 335 suite = unittest.TestSuite() |
359 | 336 |
360 variable_expander = VariableExpander(mini_installer_path, | 337 variable_expander = VariableExpander(mini_installer_path, |
361 next_version_mini_installer_path) | 338 next_version_mini_installer_path) |
362 config = ParseConfigFile(args.config, variable_expander) | 339 config = ParseConfigFile(args.config, variable_expander) |
363 | 340 |
364 RunCleanCommand(args.force_clean, variable_expander) | 341 RunCleanCommand(args.force_clean, config.states[DEFAULT_CLEAN_STATE], |
342 variable_expander) | |
365 for test in config.tests: | 343 for test in config.tests: |
366 # If tests were specified via |tests|, their names are formatted like so: | 344 # If tests were specified via |tests|, their names are formatted like so: |
367 test_name = '%s.%s.%s' % (InstallerTest.__module__, | 345 test_name = '%s.%s.%s' % (InstallerTest.__module__, |
368 InstallerTest.__name__, | 346 InstallerTest.__name__, |
369 test['name']) | 347 test['name']) |
370 if not args.test or test_name in args.test: | 348 if not args.test or test_name in args.test: |
371 suite.addTest(InstallerTest(test['name'], test['traversal'], config, | 349 suite.addTest(InstallerTest(test['name'], test['traversal'], config, |
372 variable_expander, args.quiet)) | 350 variable_expander, args.quiet)) |
373 | 351 |
374 verbosity = 2 if not args.quiet else 1 | 352 verbosity = 2 if not args.quiet else 1 |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
442 trie[path] = value | 420 trie[path] = value |
443 return | 421 return |
444 directory, rest = path.split(TEST_SEPARATOR, 1) | 422 directory, rest = path.split(TEST_SEPARATOR, 1) |
445 if directory not in trie: | 423 if directory not in trie: |
446 trie[directory] = {} | 424 trie[directory] = {} |
447 _AddPathToTrie(trie[directory], rest, value) | 425 _AddPathToTrie(trie[directory], rest, value) |
448 | 426 |
449 | 427 |
450 if __name__ == '__main__': | 428 if __name__ == '__main__': |
451 sys.exit(main()) | 429 sys.exit(main()) |
OLD | NEW |