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 |