OLD | NEW |
| (Empty) |
1 #!/usr/bin/python | |
2 | |
3 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. | |
4 # Use of this source code is governed by a BSD-style license that can be | |
5 # found in the LICENSE file. | |
6 | |
7 """This module runs a suite of Auto Update tests. | |
8 | |
9 The tests can be run on either a virtual machine or actual device depending | |
10 on parameters given. Specific tests can be run by invoking --test_prefix. | |
11 Verbose is useful for many of the tests if you want to see individual commands | |
12 being run during the update process. | |
13 """ | |
14 | |
15 import optparse | |
16 import os | |
17 import re | |
18 import sys | |
19 import unittest | |
20 | |
21 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) | |
22 import cros_build_lib as cros_lib | |
23 | |
24 import au_test | |
25 import au_worker | |
26 import dummy_au_worker | |
27 import dev_server_wrapper | |
28 import parallel_test_job | |
29 import public_key_manager | |
30 import update_exception | |
31 | |
32 def _PrepareTestSuite(options, use_dummy_worker=False): | |
33 """Returns a prepared test suite given by the options and test class.""" | |
34 au_test.AUTest.ProcessOptions(options, use_dummy_worker) | |
35 test_loader = unittest.TestLoader() | |
36 test_loader.testMethodPrefix = options.test_prefix | |
37 return test_loader.loadTestsFromTestCase(au_test.AUTest) | |
38 | |
39 | |
40 def _PregenerateUpdates(options): | |
41 """Determines all deltas that will be generated and generates them. | |
42 | |
43 This method effectively pre-generates the dev server cache for all tests. | |
44 | |
45 Args: | |
46 options: options from parsed parser. | |
47 Returns: | |
48 Dictionary of Update Identifiers->Relative cache locations. | |
49 Raises: | |
50 update_exception.UpdateException if we fail to generate an update. | |
51 """ | |
52 def _GenerateVMUpdate(target, src, private_key_path): | |
53 """Generates an update using the devserver.""" | |
54 command = ['./enter_chroot.sh', | |
55 '--', | |
56 'sudo', | |
57 'start_devserver', | |
58 '--pregenerate_update', | |
59 '--exit', | |
60 ] | |
61 # Add actual args to command. | |
62 command.append('--image=%s' % cros_lib.ReinterpretPathForChroot(target)) | |
63 if src: command.append('--src_image=%s' % | |
64 cros_lib.ReinterpretPathForChroot(src)) | |
65 if options.type == 'vm': command.append('--for_vm') | |
66 if private_key_path: | |
67 command.append('--private_key=%s' % | |
68 cros_lib.ReinterpretPathForChroot(private_key_path)) | |
69 | |
70 return cros_lib.RunCommandCaptureOutput(command, combine_stdout_stderr=True, | |
71 print_cmd=True) | |
72 | |
73 # Use dummy class to mock out updates that would be run as part of a test. | |
74 test_suite = _PrepareTestSuite(options, use_dummy_worker=True) | |
75 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite) | |
76 if not test_result.wasSuccessful(): | |
77 raise update_exception.UpdateException(1, | |
78 'Error finding updates to generate.') | |
79 | |
80 cros_lib.Info('The following delta updates are required.') | |
81 update_ids = [] | |
82 jobs = [] | |
83 args = [] | |
84 modified_images = set() | |
85 for target, srcs in dummy_au_worker.DummyAUWorker.delta_list.items(): | |
86 modified_images.add(target) | |
87 for src_key in srcs: | |
88 (src, _ , key) = src_key.partition('+') | |
89 if src: modified_images.add(src) | |
90 # TODO(sosa): Add private key as part of caching name once devserver can | |
91 # handle it its own cache. | |
92 update_id = dev_server_wrapper.GenerateUpdateId(target, src, key) | |
93 print >> sys.stderr, 'AU: %s' % update_id | |
94 update_ids.append(update_id) | |
95 jobs.append(_GenerateVMUpdate) | |
96 args.append((target, src, key)) | |
97 | |
98 # Always add the base image path. This is only useful for non-delta updates. | |
99 modified_images.add(options.base_image) | |
100 | |
101 # Add public key to all images we are using. | |
102 if options.public_key: | |
103 cros_lib.Info('Adding public keys to images for testing.') | |
104 for image in modified_images: | |
105 manager = public_key_manager.PublicKeyManager(image, options.public_key) | |
106 manager.AddKeyToImage() | |
107 au_test.AUTest.public_key_managers.append(manager) | |
108 | |
109 raw_results = parallel_test_job.RunParallelJobs(options.jobs, jobs, args, | |
110 print_status=True) | |
111 results = [] | |
112 | |
113 # Looking for this line in the output. | |
114 key_line_re = re.compile('^PREGENERATED_UPDATE=([\w/.]+)') | |
115 for result in raw_results: | |
116 (return_code, output, _) = result | |
117 if return_code != 0: | |
118 cros_lib.Warning(output) | |
119 raise update_exception.UpdateException(return_code, | |
120 'Failed to generate all updates.') | |
121 else: | |
122 for line in output.splitlines(): | |
123 match = key_line_re.search(line) | |
124 if match: | |
125 # Convert blah/blah/update.gz -> update/blah/blah. | |
126 path_to_update_gz = match.group(1).rstrip() | |
127 (path_to_update_dir, _, _) = path_to_update_gz.rpartition( | |
128 '/update.gz') | |
129 results.append('/'.join(['update', path_to_update_dir])) | |
130 break | |
131 | |
132 # Make sure all generation of updates returned cached locations. | |
133 if len(raw_results) != len(results): | |
134 raise update_exception.UpdateException( | |
135 1, 'Insufficient number cache directories returned.') | |
136 | |
137 # Build the dictionary from our id's and returned cache paths. | |
138 cache_dictionary = {} | |
139 for index, id in enumerate(update_ids): | |
140 cache_dictionary[id] = results[index] | |
141 | |
142 return cache_dictionary | |
143 | |
144 | |
145 def _RunTestsInParallel(options): | |
146 """Runs the tests given by the options in parallel.""" | |
147 threads = [] | |
148 args = [] | |
149 test_suite = _PrepareTestSuite(options) | |
150 for test in test_suite: | |
151 test_name = test.id() | |
152 test_case = unittest.TestLoader().loadTestsFromName(test_name) | |
153 threads.append(unittest.TextTestRunner(verbosity=2).run) | |
154 args.append(test_case) | |
155 | |
156 results = parallel_test_job.RunParallelJobs(options.jobs, threads, args, | |
157 print_status=False) | |
158 for test_result in results: | |
159 if not test_result.wasSuccessful(): | |
160 cros_lib.Die('Test harness was not successful') | |
161 | |
162 | |
163 def _CleanPreviousWork(options): | |
164 """Cleans up previous work from the devserver cache and local image cache.""" | |
165 cros_lib.Info('Cleaning up previous work.') | |
166 # Wipe devserver cache. | |
167 cros_lib.RunCommandCaptureOutput( | |
168 ['sudo', 'start_devserver', '--clear_cache', '--exit', ], | |
169 enter_chroot=True, print_cmd=False, combine_stdout_stderr=True) | |
170 | |
171 # Clean previous vm images if they exist. | |
172 if options.type == 'vm': | |
173 target_vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname( | |
174 options.target_image) | |
175 base_vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname( | |
176 options.base_image) | |
177 if os.path.exists(target_vm_image_path): os.remove(target_vm_image_path) | |
178 if os.path.exists(base_vm_image_path): os.remove(base_vm_image_path) | |
179 | |
180 | |
181 def main(): | |
182 parser = optparse.OptionParser() | |
183 parser.add_option('-b', '--base_image', | |
184 help='path to the base image.') | |
185 parser.add_option('-r', '--board', | |
186 help='board for the images.') | |
187 parser.add_option('--clean', default=False, dest='clean', action='store_true', | |
188 help='Clean all previous state') | |
189 parser.add_option('--no_delta', action='store_false', default=True, | |
190 dest='delta', | |
191 help='Disable using delta updates.') | |
192 parser.add_option('--no_graphics', action='store_true', | |
193 help='Disable graphics for the vm test.') | |
194 parser.add_option('-j', '--jobs', default=8, type=int, | |
195 help='Number of simultaneous jobs') | |
196 parser.add_option('--public_key', default=None, | |
197 help='Public key to use on images and updates.') | |
198 parser.add_option('--private_key', default=None, | |
199 help='Private key to use on images and updates.') | |
200 parser.add_option('-q', '--quick_test', default=False, action='store_true', | |
201 help='Use a basic test to verify image.') | |
202 parser.add_option('-m', '--remote', | |
203 help='Remote address for real test.') | |
204 parser.add_option('-t', '--target_image', | |
205 help='path to the target image.') | |
206 parser.add_option('--test_results_root', default=None, | |
207 help='Root directory to store test results. Should ' | |
208 'be defined relative to chroot root.') | |
209 parser.add_option('--test_prefix', default='test', | |
210 help='Only runs tests with specific prefix i.e. ' | |
211 'testFullUpdateWipeStateful.') | |
212 parser.add_option('-p', '--type', default='vm', | |
213 help='type of test to run: [vm, real]. Default: vm.') | |
214 parser.add_option('--verbose', default=True, action='store_true', | |
215 help='Print out rather than capture output as much as ' | |
216 'possible.') | |
217 (options, leftover_args) = parser.parse_args() | |
218 | |
219 if leftover_args: parser.error('Found unsupported flags: %s' % leftover_args) | |
220 | |
221 assert options.target_image and os.path.exists(options.target_image), \ | |
222 'Target image path does not exist' | |
223 if not options.base_image: | |
224 cros_lib.Info('Base image not specified. Using target as base image.') | |
225 options.base_image = options.target_image | |
226 | |
227 if options.private_key or options.public_key: | |
228 error_msg = ('Could not find %s key. Both private and public keys must be ' | |
229 'specified if either is specified.') | |
230 assert options.private_key and os.path.exists(options.private_key), \ | |
231 error_msg % 'private' | |
232 assert options.public_key and os.path.exists(options.public_key), \ | |
233 error_msg % 'public' | |
234 | |
235 # Clean up previous work if requested. | |
236 if options.clean: _CleanPreviousWork(options) | |
237 | |
238 # Make sure we have a log directory. | |
239 if options.test_results_root and not os.path.exists( | |
240 options.test_results_root): | |
241 os.makedirs(options.test_results_root) | |
242 | |
243 # Pre-generate update modifies images by adding public keys to them. | |
244 # Wrap try to make sure we clean this up before we're done. | |
245 try: | |
246 # Generate cache of updates to use during test harness. | |
247 update_cache = _PregenerateUpdates(options) | |
248 au_worker.AUWorker.SetUpdateCache(update_cache) | |
249 | |
250 my_server = dev_server_wrapper.DevServerWrapper( | |
251 au_test.AUTest.test_results_root) | |
252 my_server.start() | |
253 try: | |
254 if options.type == 'vm': | |
255 _RunTestsInParallel(options) | |
256 else: | |
257 # TODO(sosa) - Take in a machine pool for a real test. | |
258 # Can't run in parallel with only one remote device. | |
259 test_suite = _PrepareTestSuite(options) | |
260 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) | |
261 if not test_result.wasSuccessful(): cros_lib.Die('Test harness failed.') | |
262 | |
263 finally: | |
264 my_server.Stop() | |
265 | |
266 finally: | |
267 # Un-modify any target images we modified. We don't need to un-modify | |
268 # non-targets because they aren't important for archival steps. | |
269 if options.public_key: | |
270 cros_lib.Info('Cleaning up. Removing keys added as part of testing.') | |
271 target_directory = os.path.dirname(options.target_image) | |
272 for key_manager in au_test.AUTest.public_key_managers: | |
273 if key_manager.image_path.startswith(target_directory): | |
274 key_manager.RemoveKeyFromImage() | |
275 | |
276 | |
277 if __name__ == '__main__': | |
278 main() | |
OLD | NEW |