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

Side by Side Diff: chrome/test/mini_installer/test_installer.py

Issue 23523045: Clean the machine before running commands in the mini_installer test framework. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Address gab's comments. Created 7 years, 3 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 """
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
45 Args: 45 Args:
46 test: An array of alternating state names and action names, starting and 46 test: An array of alternating state names and action names, starting and
47 ending with state names. 47 ending with state names.
48 config: The Config object. 48 config: The Config object.
49 path_resolver: A PathResolver object. 49 path_resolver: A PathResolver object.
50 """ 50 """
51 super(InstallerTest, self).__init__() 51 super(InstallerTest, self).__init__()
52 self._test = test 52 self._test = test
53 self._config = config 53 self._config = config
54 self._path_resolver = path_resolver 54 self._path_resolver = path_resolver
55 self._was_successful = False
55 56
56 def __str__(self): 57 def __str__(self):
57 """Returns a string representing the test case. 58 """Returns a string representing the test case.
58 59
59 Returns: 60 Returns:
60 A string created by joining state names and action names together with 61 A string created by joining state names and action names together with
61 ' -> ', for example, 'Test: clean -> install chrome -> chrome_installed'. 62 ' -> ', for example, 'Test: clean -> install chrome -> chrome_installed'.
62 """ 63 """
63 return 'Test: %s' % (' -> '.join(self._test)) 64 return 'Test: %s' % (' -> '.join(self._test))
64 65
65 def runTest(self): 66 def runTest(self):
66 """Run the test case.""" 67 """Run the test case."""
67 # |test| is an array of alternating state names and action names, starting 68 # |test| is an array of alternating state names and action names, starting
68 # and ending with state names. Therefore, its length must be odd. 69 # and ending with state names. Therefore, its length must be odd.
69 self.assertEqual(1, len(self._test) % 2, 70 self.assertEqual(1, len(self._test) % 2,
70 'The length of test array must be odd') 71 'The length of test array must be odd')
71 72
72 # TODO(sukolsak): run a reset command that puts the machine in clean state.
73
74 state = self._test[0] 73 state = self._test[0]
75 self._VerifyState(state) 74 self._VerifyState(state)
76 75
77 # Starting at index 1, we loop through pairs of (action, state). 76 # Starting at index 1, we loop through pairs of (action, state).
78 for i in range(1, len(self._test), 2): 77 for i in range(1, len(self._test), 2):
79 action = self._test[i] 78 action = self._test[i]
80 self._RunCommand(self._config.actions[action]) 79 RunCommand(self._config.actions[action], self._path_resolver)
81 80
82 state = self._test[i + 1] 81 state = self._test[i + 1]
83 self._VerifyState(state) 82 self._VerifyState(state)
84 83
84 self._was_successful = True
gab 2013/09/16 18:33:45 Add a comment stating why the test is successful i
sukolsak 2013/09/16 22:02:02 Done.
85
86 def tearDown(self):
87 """Cleans up the machine if the test case fails or raises an exception."""
gab 2013/09/16 18:33:45 Isn't "fail" and "raised an exception" the same th
sukolsak 2013/09/16 22:02:02 Done. Python unittest makes a distinction between
88 if not self._was_successful:
gab 2013/09/16 18:41:22 Rename |_was_successful| to something like |clean_
sukolsak 2013/09/16 22:02:02 Done.
89 RunCleanCommand(True, self._path_resolver)
90
85 def shortDescription(self): 91 def shortDescription(self):
86 """Overridden from unittest.TestCase. 92 """Overridden from unittest.TestCase.
87 93
88 We return None as the short description to suppress its printing. 94 We return None as the short description to suppress its printing.
89 The default implementation of this method returns the docstring of the 95 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. 96 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. 97 The description from the __str__ method is informative enough.
92 """ 98 """
93 return None 99 return None
94 100
95 def _VerifyState(self, state): 101 def _VerifyState(self, state):
96 """Verifies that the current machine state matches a given state. 102 """Verifies that the current machine state matches a given state.
97 103
98 Args: 104 Args:
99 state: A state name. 105 state: A state name.
100 """ 106 """
101 try: 107 try:
102 verifier.Verify(self._config.states[state], self._path_resolver) 108 verifier.Verify(self._config.states[state], self._path_resolver)
103 except AssertionError as e: 109 except AssertionError as e:
104 # If an AssertionError occurs, we intercept it and add the state name 110 # 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. 111 # to the error message so that we know where the test fails.
106 raise AssertionError("In state '%s', %s" % (state, e)) 112 raise AssertionError("In state '%s', %s" % (state, e))
107 113
108 def _RunCommand(self, command):
109 """Runs the given command from the current file's directory.
110 114
111 Args: 115 def RunCommand(command, path_resolver):
112 command: A command to run. It is expanded using ResolvePath. 116 """Runs the given command from the current file's directory.
113 """ 117
114 resolved_command = self._path_resolver.ResolvePath(command) 118 This function throws an Exception if the command returns with non-zero exit
115 script_dir = os.path.dirname(os.path.abspath(__file__)) 119 status.
116 exit_status = subprocess.call(resolved_command, shell=True, cwd=script_dir) 120
117 if exit_status != 0: 121 Args:
118 self.fail('Command %s returned non-zero exit status %s' % ( 122 command: A command to run. It is expanded using ResolvePath.
119 resolved_command, exit_status)) 123 path_resolver: A PathResolver object.
124 """
125 resolved_command = path_resolver.ResolvePath(command)
126 script_dir = os.path.dirname(os.path.abspath(__file__))
127 exit_status = subprocess.call(resolved_command, shell=True, cwd=script_dir)
128 if exit_status != 0:
129 raise Exception('Command %s returned non-zero exit status %s' % (
gab 2013/09/16 18:41:22 Why go from self.fail to throwing an exception? M
sukolsak 2013/09/16 22:02:02 Because RunCommand is now a global function. I cha
gab 2013/09/16 22:15:50 Ah I see, man python is so subtle for that, uninde
130 command, exit_status))
131
132
133 def RunCleanCommand(force_clean, path_resolver):
134 """Puts the machine in the clean state (i.e. Chrome not installed).
135
136 Args:
137 force_clean: A boolean indicating whether to force cleaning existing
138 installations.
139 path_resolver: A PathResolver object.
140 """
141 # TODO(sukolsak): Read the clean state from the config file and clean
142 # the machine according to it.
143 # TODO(sukolsak): Handle Chrome SxS installs.
144 commands = []
145 interactive_option = '--interactive' if not force_clean else ''
146 for level_option in ['', '--system-level']:
147 commands.append('python uninstall_chrome.py '
148 '--chrome-long-name="$CHROME_LONG_NAME" '
149 '--no-error-if-absent %s %s' %
150 (level_option, interactive_option))
151 RunCommand(' && '.join(commands), path_resolver)
120 152
121 153
122 def MergePropertyDictionaries(current_property, new_property): 154 def MergePropertyDictionaries(current_property, new_property):
123 """Merges the new property dictionary into the current property dictionary. 155 """Merges the new property dictionary into the current property dictionary.
124 156
125 This is different from general dictionary merging in that, in case there are 157 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 158 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 159 override earlier values in the second level. For more details, take a look at
128 http://goo.gl/uE0RoR 160 http://goo.gl/uE0RoR
129 161
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
178 config = Config() 210 config = Config()
179 config.tests = config_data['tests'] 211 config.tests = config_data['tests']
180 for state_name, state_property_filenames in config_data['states']: 212 for state_name, state_property_filenames in config_data['states']:
181 config.states[state_name] = ParsePropertyFiles(directory, 213 config.states[state_name] = ParsePropertyFiles(directory,
182 state_property_filenames) 214 state_property_filenames)
183 for action_name, action_command in config_data['actions']: 215 for action_name, action_command in config_data['actions']:
184 config.actions[action_name] = action_command 216 config.actions[action_name] = action_command
185 return config 217 return config
186 218
187 219
188 def RunTests(mini_installer_path, config): 220 def RunTests(mini_installer_path, config, force_clean):
189 """Tests the installer using the given Config object. 221 """Tests the installer using the given Config object.
190 222
191 Args: 223 Args:
192 mini_installer_path: The path to mini_installer.exe. 224 mini_installer_path: The path to mini_installer.exe.
193 config: A Config object. 225 config: A Config object.
226 force_clean: A boolean indicating whether to force cleaning existing
227 installations.
194 228
195 Returns: 229 Returns:
196 True if all the tests passed, or False otherwise. 230 True if all the tests passed, or False otherwise.
197 """ 231 """
198 suite = unittest.TestSuite() 232 suite = unittest.TestSuite()
199 path_resolver = PathResolver(mini_installer_path) 233 path_resolver = PathResolver(mini_installer_path)
234 RunCleanCommand(force_clean, path_resolver)
200 for test in config.tests: 235 for test in config.tests:
201 suite.addTest(InstallerTest(test, config, path_resolver)) 236 suite.addTest(InstallerTest(test, config, path_resolver))
202 result = unittest.TextTestRunner(verbosity=2).run(suite) 237 result = unittest.TextTestRunner(verbosity=2).run(suite)
203 return result.wasSuccessful() 238 return result.wasSuccessful()
204 239
205 240
206 def main(): 241 def main():
207 usage = 'usage: %prog [options] config_filename' 242 usage = 'usage: %prog [options] config_filename'
208 parser = optparse.OptionParser(usage, description='Test the installer.') 243 parser = optparse.OptionParser(usage, description='Test the installer.')
209 parser.add_option('--build-dir', default='out', 244 parser.add_option('--build-dir', default='out',
210 help='Path to main build directory (the parent of the ' 245 help='Path to main build directory (the parent of the '
211 'Release or Debug directory)') 246 'Release or Debug directory)')
212 parser.add_option('--target', default='Release', 247 parser.add_option('--target', default='Release',
213 help='Build target (Release or Debug)') 248 help='Build target (Release or Debug)')
249 parser.add_option('--force-clean', action='store_true', dest='force_clean',
250 default=False, help='Force cleaning existing installations')
214 options, args = parser.parse_args() 251 options, args = parser.parse_args()
215 if len(args) != 1: 252 if len(args) != 1:
216 parser.error('Incorrect number of arguments.') 253 parser.error('Incorrect number of arguments.')
217 config_filename = args[0] 254 config_filename = args[0]
218 255
219 mini_installer_path = os.path.join(options.build_dir, options.target, 256 mini_installer_path = os.path.join(options.build_dir, options.target,
220 'mini_installer.exe') 257 'mini_installer.exe')
221 assert os.path.exists(mini_installer_path), ('Could not find file %s' % 258 assert os.path.exists(mini_installer_path), ('Could not find file %s' %
222 mini_installer_path) 259 mini_installer_path)
223 config = ParseConfigFile(config_filename) 260 config = ParseConfigFile(config_filename)
224 if not RunTests(mini_installer_path, config): 261 if not RunTests(mini_installer_path, config, options.force_clean):
225 return 1 262 return 1
226 return 0 263 return 0
227 264
228 265
229 if __name__ == '__main__': 266 if __name__ == '__main__':
230 sys.exit(main()) 267 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | chrome/test/mini_installer/uninstall_chrome.py » ('j') | chrome/test/mini_installer/uninstall_chrome.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698