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 """ |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 62 """ | 62 """ |
| 63 return 'Test: %s' % (' -> '.join(self._test)) | 63 return 'Test: %s' % (' -> '.join(self._test)) |
| 64 | 64 |
| 65 def runTest(self): | 65 def runTest(self): |
| 66 """Run the test case.""" | 66 """Run the test case.""" |
| 67 # |test| is an array of alternating state names and action names, starting | 67 # |test| is an array of alternating state names and action names, starting |
| 68 # and ending with state names. Therefore, its length must be odd. | 68 # and ending with state names. Therefore, its length must be odd. |
| 69 self.assertEqual(1, len(self._test) % 2, | 69 self.assertEqual(1, len(self._test) % 2, |
| 70 'The length of test array must be odd') | 70 'The length of test array must be odd') |
| 71 | 71 |
| 72 # TODO(sukolsak): run a reset command that puts the machine in clean state. | |
| 73 | |
| 74 state = self._test[0] | 72 state = self._test[0] |
| 75 self._VerifyState(state) | 73 self._VerifyState(state) |
| 76 | 74 |
| 77 # Starting at index 1, we loop through pairs of (action, state). | 75 # Starting at index 1, we loop through pairs of (action, state). |
| 78 for i in range(1, len(self._test), 2): | 76 for i in range(1, len(self._test), 2): |
| 79 action = self._test[i] | 77 action = self._test[i] |
| 80 self._RunCommand(self._config.actions[action]) | 78 command = self._config.actions[action] |
| 79 resolved_command = self._path_resolver.ResolvePath(command) | |
| 80 RunCommand(resolved_command) | |
| 81 | 81 |
| 82 state = self._test[i + 1] | 82 state = self._test[i + 1] |
| 83 self._VerifyState(state) | 83 self._VerifyState(state) |
| 84 | 84 |
| 85 def tearDown(self): | |
| 86 """Cleans up the machine after running the test case.""" | |
| 87 RunCleanCommand(True) | |
|
gab
2013/09/16 13:59:58
We should only need to do this if the test case fa
sukolsak
2013/09/16 18:19:22
Done.
| |
| 88 | |
| 85 def shortDescription(self): | 89 def shortDescription(self): |
| 86 """Overridden from unittest.TestCase. | 90 """Overridden from unittest.TestCase. |
| 87 | 91 |
| 88 We return None as the short description to suppress its printing. | 92 We return None as the short description to suppress its printing. |
| 89 The default implementation of this method returns the docstring of the | 93 The default implementation of this method returns the docstring of the |
| 90 runTest method, which is not useful since it's the same for every test case. | 94 runTest method, which is not useful since it's the same for every test case. |
| 91 The description from the __str__ method is informative enough. | 95 The description from the __str__ method is informative enough. |
| 92 """ | 96 """ |
| 93 return None | 97 return None |
| 94 | 98 |
| 95 def _VerifyState(self, state): | 99 def _VerifyState(self, state): |
| 96 """Verifies that the current machine state matches a given state. | 100 """Verifies that the current machine state matches a given state. |
| 97 | 101 |
| 98 Args: | 102 Args: |
| 99 state: A state name. | 103 state: A state name. |
| 100 """ | 104 """ |
| 101 try: | 105 try: |
| 102 verifier.Verify(self._config.states[state], self._path_resolver) | 106 verifier.Verify(self._config.states[state], self._path_resolver) |
| 103 except AssertionError as e: | 107 except AssertionError as e: |
| 104 # If an AssertionError occurs, we intercept it and add the state name | 108 # If an AssertionError occurs, we intercept it and add the state name |
| 105 # to the error message so that we know where the test fails. | 109 # to the error message so that we know where the test fails. |
| 106 raise AssertionError("In state '%s', %s" % (state, e)) | 110 raise AssertionError("In state '%s', %s" % (state, e)) |
| 107 | 111 |
| 108 def _RunCommand(self, command): | |
| 109 """Runs the given command from the current file's directory. | |
| 110 | 112 |
| 111 Args: | 113 def RunCommand(command): |
| 112 command: A command to run. It is expanded using ResolvePath. | 114 """Runs the given command from the current file's directory. |
| 113 """ | 115 |
| 114 resolved_command = self._path_resolver.ResolvePath(command) | 116 This function throws an Exception if the command returns with non-zero exit |
| 115 script_dir = os.path.dirname(os.path.abspath(__file__)) | 117 status. |
| 116 exit_status = subprocess.call(resolved_command, shell=True, cwd=script_dir) | 118 |
| 117 if exit_status != 0: | 119 Args: |
| 118 self.fail('Command %s returned non-zero exit status %s' % ( | 120 command: A command to run. |
| 119 resolved_command, exit_status)) | 121 """ |
| 122 script_dir = os.path.dirname(os.path.abspath(__file__)) | |
| 123 exit_status = subprocess.call(command, shell=True, cwd=script_dir) | |
| 124 if exit_status != 0: | |
| 125 raise Exception('Command %s returned non-zero exit status %s' % ( | |
| 126 command, exit_status)) | |
| 127 | |
| 128 | |
| 129 def RunCleanCommand(force_clean): | |
| 130 """Puts the machine in the clean state (i.e. Chrome not installed). | |
| 131 | |
| 132 Args: | |
| 133 force_clean: A boolean indicating whether to force cleaning existing | |
| 134 installations. | |
| 135 """ | |
| 136 # TODO(sukolsak): Read the clean state from the config file and clean | |
| 137 # the machine according to it. | |
| 138 # TODO(sukolsak): Handle Chrome SxS installs. | |
| 139 commands = [] | |
| 140 interactive_option = '--interactive' if not force_clean else '' | |
| 141 for chrome_long_name in ['Google Chrome', 'Chromium']: | |
|
gab
2013/09/16 13:59:58
I don't like having to delete both brands; imo we
sukolsak
2013/09/16 18:19:22
Done.
| |
| 142 for level_option in ['', '--system-level']: | |
| 143 commands.append('python uninstall_chrome.py --chrome-long-name="%s" ' | |
| 144 '--no-error-if-absent %s %s' % | |
| 145 (chrome_long_name, level_option, interactive_option)) | |
| 146 RunCommand(' && '.join(commands)) | |
| 120 | 147 |
| 121 | 148 |
| 122 def MergePropertyDictionaries(current_property, new_property): | 149 def MergePropertyDictionaries(current_property, new_property): |
| 123 """Merges the new property dictionary into the current property dictionary. | 150 """Merges the new property dictionary into the current property dictionary. |
| 124 | 151 |
| 125 This is different from general dictionary merging in that, in case there are | 152 This is different from general dictionary merging in that, in case there are |
| 126 keys with the same name, we merge values together in the first level, and we | 153 keys with the same name, we merge values together in the first level, and we |
| 127 override earlier values in the second level. For more details, take a look at | 154 override earlier values in the second level. For more details, take a look at |
| 128 http://goo.gl/uE0RoR | 155 http://goo.gl/uE0RoR |
| 129 | 156 |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 178 config = Config() | 205 config = Config() |
| 179 config.tests = config_data['tests'] | 206 config.tests = config_data['tests'] |
| 180 for state_name, state_property_filenames in config_data['states']: | 207 for state_name, state_property_filenames in config_data['states']: |
| 181 config.states[state_name] = ParsePropertyFiles(directory, | 208 config.states[state_name] = ParsePropertyFiles(directory, |
| 182 state_property_filenames) | 209 state_property_filenames) |
| 183 for action_name, action_command in config_data['actions']: | 210 for action_name, action_command in config_data['actions']: |
| 184 config.actions[action_name] = action_command | 211 config.actions[action_name] = action_command |
| 185 return config | 212 return config |
| 186 | 213 |
| 187 | 214 |
| 188 def RunTests(mini_installer_path, config): | 215 def RunTests(mini_installer_path, config, force_clean): |
| 189 """Tests the installer using the given Config object. | 216 """Tests the installer using the given Config object. |
| 190 | 217 |
| 191 Args: | 218 Args: |
| 192 mini_installer_path: The path to mini_installer.exe. | 219 mini_installer_path: The path to mini_installer.exe. |
| 193 config: A Config object. | 220 config: A Config object. |
| 221 force_clean: A boolean indicating whether to force cleaning existing | |
| 222 installations. | |
| 194 | 223 |
| 195 Returns: | 224 Returns: |
| 196 True if all the tests passed, or False otherwise. | 225 True if all the tests passed, or False otherwise. |
| 197 """ | 226 """ |
| 227 RunCleanCommand(force_clean) | |
| 198 suite = unittest.TestSuite() | 228 suite = unittest.TestSuite() |
| 199 path_resolver = PathResolver(mini_installer_path) | 229 path_resolver = PathResolver(mini_installer_path) |
| 200 for test in config.tests: | 230 for test in config.tests: |
| 201 suite.addTest(InstallerTest(test, config, path_resolver)) | 231 suite.addTest(InstallerTest(test, config, path_resolver)) |
| 202 result = unittest.TextTestRunner(verbosity=2).run(suite) | 232 result = unittest.TextTestRunner(verbosity=2).run(suite) |
| 203 return result.wasSuccessful() | 233 return result.wasSuccessful() |
| 204 | 234 |
| 205 | 235 |
| 206 def main(): | 236 def main(): |
| 207 usage = 'usage: %prog [options] config_filename' | 237 usage = 'usage: %prog [options] config_filename' |
| 208 parser = optparse.OptionParser(usage, description='Test the installer.') | 238 parser = optparse.OptionParser(usage, description='Test the installer.') |
| 209 parser.add_option('--build-dir', default='out', | 239 parser.add_option('--build-dir', default='out', |
| 210 help='Path to main build directory (the parent of the ' | 240 help='Path to main build directory (the parent of the ' |
| 211 'Release or Debug directory)') | 241 'Release or Debug directory)') |
| 212 parser.add_option('--target', default='Release', | 242 parser.add_option('--target', default='Release', |
| 213 help='Build target (Release or Debug)') | 243 help='Build target (Release or Debug)') |
| 244 parser.add_option('--force-clean', action='store_true', dest='force_clean', | |
| 245 default=False, help='Force cleaning existing installations') | |
| 214 options, args = parser.parse_args() | 246 options, args = parser.parse_args() |
| 215 if len(args) != 1: | 247 if len(args) != 1: |
| 216 parser.error('Incorrect number of arguments.') | 248 parser.error('Incorrect number of arguments.') |
| 217 config_filename = args[0] | 249 config_filename = args[0] |
| 218 | 250 |
| 219 mini_installer_path = os.path.join(options.build_dir, options.target, | 251 mini_installer_path = os.path.join(options.build_dir, options.target, |
| 220 'mini_installer.exe') | 252 'mini_installer.exe') |
| 221 assert os.path.exists(mini_installer_path), ('Could not find file %s' % | 253 assert os.path.exists(mini_installer_path), ('Could not find file %s' % |
| 222 mini_installer_path) | 254 mini_installer_path) |
| 223 config = ParseConfigFile(config_filename) | 255 config = ParseConfigFile(config_filename) |
| 224 if not RunTests(mini_installer_path, config): | 256 if not RunTests(mini_installer_path, config, options.force_clean): |
| 225 return 1 | 257 return 1 |
| 226 return 0 | 258 return 0 |
| 227 | 259 |
| 228 | 260 |
| 229 if __name__ == '__main__': | 261 if __name__ == '__main__': |
| 230 sys.exit(main()) | 262 sys.exit(main()) |
| OLD | NEW |