Chromium Code Reviews| 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 |