OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 | 2 |
3 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. | 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 | 4 # Use of this source code is governed by a BSD-style license that can be |
5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
6 | 6 |
7 """This module runs a suite of Auto Update tests. | 7 """This module runs a suite of Auto Update tests. |
8 | 8 |
9 The tests can be run on either a virtual machine or actual device depending | 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. | 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 | 11 Verbose is useful for many of the tests if you want to see individual commands |
12 being run during the update process. | 12 being run during the update process. |
13 """ | 13 """ |
14 | 14 |
15 import optparse | 15 import optparse |
16 import os | 16 import os |
17 import re | 17 import re |
18 import sys | 18 import sys |
19 import tempfile | |
20 import unittest | 19 import unittest |
21 | 20 |
22 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) | 21 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) |
23 import cros_build_lib as cros_lib | 22 import cros_build_lib as cros_lib |
24 | 23 |
25 import au_test | 24 import au_test |
26 import au_worker | 25 import au_worker |
27 import dummy_au_worker | 26 import dummy_au_worker |
28 import dev_server_wrapper | 27 import dev_server_wrapper |
29 import parallel_test_job | 28 import parallel_test_job |
| 29 import public_key_manager |
30 import update_exception | 30 import update_exception |
31 | 31 |
32 | |
33 def _PrepareTestSuite(options, use_dummy_worker=False): | 32 def _PrepareTestSuite(options, use_dummy_worker=False): |
34 """Returns a prepared test suite given by the options and test class.""" | 33 """Returns a prepared test suite given by the options and test class.""" |
35 au_test.AUTest.ProcessOptions(options, use_dummy_worker) | 34 au_test.AUTest.ProcessOptions(options, use_dummy_worker) |
36 test_loader = unittest.TestLoader() | 35 test_loader = unittest.TestLoader() |
37 test_loader.testMethodPrefix = options.test_prefix | 36 test_loader.testMethodPrefix = options.test_prefix |
38 return test_loader.loadTestsFromTestCase(au_test.AUTest) | 37 return test_loader.loadTestsFromTestCase(au_test.AUTest) |
39 | 38 |
40 | 39 |
41 def _PregenerateUpdates(options): | 40 def _PregenerateUpdates(options): |
42 """Determines all deltas that will be generated and generates them. | 41 """Determines all deltas that will be generated and generates them. |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
75 test_suite = _PrepareTestSuite(options, use_dummy_worker=True) | 74 test_suite = _PrepareTestSuite(options, use_dummy_worker=True) |
76 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite) | 75 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite) |
77 if not test_result.wasSuccessful(): | 76 if not test_result.wasSuccessful(): |
78 raise update_exception.UpdateException(1, | 77 raise update_exception.UpdateException(1, |
79 'Error finding updates to generate.') | 78 'Error finding updates to generate.') |
80 | 79 |
81 cros_lib.Info('The following delta updates are required.') | 80 cros_lib.Info('The following delta updates are required.') |
82 update_ids = [] | 81 update_ids = [] |
83 jobs = [] | 82 jobs = [] |
84 args = [] | 83 args = [] |
| 84 modified_images = set() |
85 for target, srcs in dummy_au_worker.DummyAUWorker.delta_list.items(): | 85 for target, srcs in dummy_au_worker.DummyAUWorker.delta_list.items(): |
| 86 modified_images.add(target) |
86 for src_key in srcs: | 87 for src_key in srcs: |
87 (src, _ , key) = src_key.partition('+') | 88 (src, _ , key) = src_key.partition('+') |
| 89 if src: modified_images.add(src) |
88 # TODO(sosa): Add private key as part of caching name once devserver can | 90 # TODO(sosa): Add private key as part of caching name once devserver can |
89 # handle it its own cache. | 91 # handle it its own cache. |
90 update_id = dev_server_wrapper.GenerateUpdateId(target, src, key) | 92 update_id = dev_server_wrapper.GenerateUpdateId(target, src, key) |
91 print >> sys.stderr, 'AU: %s' % update_id | 93 print >> sys.stderr, 'AU: %s' % update_id |
92 update_ids.append(update_id) | 94 update_ids.append(update_id) |
93 jobs.append(_GenerateVMUpdate) | 95 jobs.append(_GenerateVMUpdate) |
94 args.append((target, src, key)) | 96 args.append((target, src, key)) |
95 | 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 |
96 raw_results = parallel_test_job.RunParallelJobs(options.jobs, jobs, args, | 109 raw_results = parallel_test_job.RunParallelJobs(options.jobs, jobs, args, |
97 print_status=True) | 110 print_status=True) |
98 results = [] | 111 results = [] |
99 | 112 |
100 # Looking for this line in the output. | 113 # Looking for this line in the output. |
101 key_line_re = re.compile('^PREGENERATED_UPDATE=([\w/.]+)') | 114 key_line_re = re.compile('^PREGENERATED_UPDATE=([\w/.]+)') |
102 for result in raw_results: | 115 for result in raw_results: |
103 (return_code, output, _) = result | 116 (return_code, output, _) = result |
104 if return_code != 0: | 117 if return_code != 0: |
105 cros_lib.Warning(output) | 118 cros_lib.Warning(output) |
(...skipping 24 matching lines...) Expand all Loading... |
130 | 143 |
131 | 144 |
132 def _RunTestsInParallel(options): | 145 def _RunTestsInParallel(options): |
133 """Runs the tests given by the options in parallel.""" | 146 """Runs the tests given by the options in parallel.""" |
134 threads = [] | 147 threads = [] |
135 args = [] | 148 args = [] |
136 test_suite = _PrepareTestSuite(options) | 149 test_suite = _PrepareTestSuite(options) |
137 for test in test_suite: | 150 for test in test_suite: |
138 test_name = test.id() | 151 test_name = test.id() |
139 test_case = unittest.TestLoader().loadTestsFromName(test_name) | 152 test_case = unittest.TestLoader().loadTestsFromName(test_name) |
140 threads.append(unittest.TextTestRunner().run) | 153 threads.append(unittest.TextTestRunner(verbosity=2).run) |
141 args.append(test_case) | 154 args.append(test_case) |
142 | 155 |
143 results = parallel_test_job.RunParallelJobs(options.jobs, threads, args, | 156 results = parallel_test_job.RunParallelJobs(options.jobs, threads, args, |
144 print_status=False) | 157 print_status=False) |
145 for test_result in results: | 158 for test_result in results: |
146 if not test_result.wasSuccessful(): | 159 if not test_result.wasSuccessful(): |
147 cros_lib.Die('Test harness was not successful') | 160 cros_lib.Die('Test harness was not successful') |
148 | 161 |
149 | 162 |
150 def _InsertPublicKeyIntoImage(image_path, key_path): | |
151 """Inserts public key into image @ static update_engine location.""" | |
152 from_dir = os.path.dirname(image_path) | |
153 image = os.path.basename(image_path) | |
154 crosutils_dir = os.path.abspath(__file__).rsplit('/', 2)[0] | |
155 target_key_path = 'usr/share/update_engine/update-payload-key.pub.pem' | |
156 | |
157 # Temporary directories for this function. | |
158 rootfs_dir = tempfile.mkdtemp(suffix='rootfs', prefix='tmp') | |
159 stateful_dir = tempfile.mkdtemp(suffix='stateful', prefix='tmp') | |
160 | |
161 cros_lib.Info('Copying %s into %s' % (key_path, image_path)) | |
162 try: | |
163 cros_lib.RunCommand(['./mount_gpt_image.sh', | |
164 '--from=%s' % from_dir, | |
165 '--image=%s' % image, | |
166 '--rootfs_mountpt=%s' % rootfs_dir, | |
167 '--stateful_mountpt=%s' % stateful_dir, | |
168 ], print_cmd=False, redirect_stdout=True, | |
169 redirect_stderr=True, cwd=crosutils_dir) | |
170 path = os.path.join(rootfs_dir, target_key_path) | |
171 dir_path = os.path.dirname(path) | |
172 cros_lib.RunCommand(['sudo', 'mkdir', '--parents', dir_path], | |
173 print_cmd=False) | |
174 cros_lib.RunCommand(['sudo', 'cp', '--force', '-p', key_path, path], | |
175 print_cmd=False) | |
176 finally: | |
177 # Unmount best effort regardless. | |
178 cros_lib.RunCommand(['./mount_gpt_image.sh', | |
179 '--unmount', | |
180 '--rootfs_mountpt=%s' % rootfs_dir, | |
181 '--stateful_mountpt=%s' % stateful_dir, | |
182 ], print_cmd=False, redirect_stdout=True, | |
183 redirect_stderr=True, cwd=crosutils_dir) | |
184 # Clean up our directories. | |
185 os.rmdir(rootfs_dir) | |
186 os.rmdir(stateful_dir) | |
187 | |
188 cros_lib.RunCommand(['bin/cros_make_image_bootable', | |
189 cros_lib.ReinterpretPathForChroot(from_dir), | |
190 image], | |
191 print_cmd=False, redirect_stdout=True, | |
192 redirect_stderr=True, enter_chroot=True, | |
193 cwd=crosutils_dir) | |
194 | |
195 | |
196 def _CleanPreviousWork(options): | 163 def _CleanPreviousWork(options): |
197 """Cleans up previous work from the devserver cache and local image cache.""" | 164 """Cleans up previous work from the devserver cache and local image cache.""" |
198 cros_lib.Info('Cleaning up previous work.') | 165 cros_lib.Info('Cleaning up previous work.') |
199 # Wipe devserver cache. | 166 # Wipe devserver cache. |
200 cros_lib.RunCommandCaptureOutput( | 167 cros_lib.RunCommandCaptureOutput( |
201 ['sudo', 'start_devserver', '--clear_cache', '--exit', ], | 168 ['sudo', 'start_devserver', '--clear_cache', '--exit', ], |
202 enter_chroot=True, print_cmd=False, combine_stdout_stderr=True) | 169 enter_chroot=True, print_cmd=False, combine_stdout_stderr=True) |
203 | 170 |
204 # Clean previous vm images if they exist. | 171 # Clean previous vm images if they exist. |
205 if options.type == 'vm': | 172 if options.type == 'vm': |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
250 (options, leftover_args) = parser.parse_args() | 217 (options, leftover_args) = parser.parse_args() |
251 | 218 |
252 if leftover_args: parser.error('Found unsupported flags: %s' % leftover_args) | 219 if leftover_args: parser.error('Found unsupported flags: %s' % leftover_args) |
253 | 220 |
254 assert options.target_image and os.path.exists(options.target_image), \ | 221 assert options.target_image and os.path.exists(options.target_image), \ |
255 'Target image path does not exist' | 222 'Target image path does not exist' |
256 if not options.base_image: | 223 if not options.base_image: |
257 cros_lib.Info('Base image not specified. Using target as base image.') | 224 cros_lib.Info('Base image not specified. Using target as base image.') |
258 options.base_image = options.target_image | 225 options.base_image = options.target_image |
259 | 226 |
260 # Sanity checks on keys and insert them onto the image. The caches must be | |
261 # cleaned so we know that the vm images and payloads match the possibly new | |
262 # key. | |
263 if options.private_key or options.public_key: | 227 if options.private_key or options.public_key: |
264 error_msg = ('Could not find %s key. Both private and public keys must be ' | 228 error_msg = ('Could not find %s key. Both private and public keys must be ' |
265 'specified if either is specified.') | 229 'specified if either is specified.') |
266 assert options.private_key and os.path.exists(options.private_key), \ | 230 assert options.private_key and os.path.exists(options.private_key), \ |
267 error_msg % 'private' | 231 error_msg % 'private' |
268 assert options.public_key and os.path.exists(options.public_key), \ | 232 assert options.public_key and os.path.exists(options.public_key), \ |
269 error_msg % 'public' | 233 error_msg % 'public' |
270 _InsertPublicKeyIntoImage(options.target_image, options.public_key) | |
271 if options.target_image != options.base_image: | |
272 _InsertPublicKeyIntoImage(options.base_image, options.public_key) | |
273 options.clean = True | |
274 | 234 |
275 # Clean up previous work if requested. | 235 # Clean up previous work if requested. |
276 if options.clean: _CleanPreviousWork(options) | 236 if options.clean: _CleanPreviousWork(options) |
277 | 237 |
278 # Make sure we have a log directory. | 238 # Make sure we have a log directory. |
279 if not os.path.exists(options.test_results_root): | 239 if not os.path.exists(options.test_results_root): |
280 os.makedirs(options.test_results_root) | 240 os.makedirs(options.test_results_root) |
281 | 241 |
282 # Generate cache of updates to use during test harness. | 242 # Pre-generate update modifies images by adding public keys to them. |
283 update_cache = _PregenerateUpdates(options) | 243 # Wrap try to make sure we clean this up before we're done. |
284 au_worker.AUWorker.SetUpdateCache(update_cache) | 244 try: |
| 245 # Generate cache of updates to use during test harness. |
| 246 update_cache = _PregenerateUpdates(options) |
| 247 au_worker.AUWorker.SetUpdateCache(update_cache) |
285 | 248 |
286 my_server = dev_server_wrapper.DevServerWrapper( | 249 my_server = dev_server_wrapper.DevServerWrapper( |
287 au_test.AUTest.test_results_root) | 250 au_test.AUTest.test_results_root) |
288 my_server.start() | 251 my_server.start() |
289 try: | 252 try: |
290 if options.type == 'vm': | 253 if options.type == 'vm': |
291 _RunTestsInParallel(options) | 254 _RunTestsInParallel(options) |
292 else: | 255 else: |
293 # TODO(sosa) - Take in a machine pool for a real test. | 256 # TODO(sosa) - Take in a machine pool for a real test. |
294 # Can't run in parallel with only one remote device. | 257 # Can't run in parallel with only one remote device. |
295 test_suite = _PrepareTestSuite(options) | 258 test_suite = _PrepareTestSuite(options) |
296 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) | 259 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) |
297 if not test_result.wasSuccessful(): cros_lib.Die('Test harness failed.') | 260 if not test_result.wasSuccessful(): cros_lib.Die('Test harness failed.') |
| 261 |
| 262 finally: |
| 263 my_server.Stop() |
| 264 |
298 finally: | 265 finally: |
299 my_server.Stop() | 266 # Un-modify any target images we modified. We don't need to un-modify |
| 267 # non-targets because they aren't important for archival steps. |
| 268 if options.public_key: |
| 269 cros_lib.Info('Cleaning up. Removing keys added as part of testing.') |
| 270 target_directory = os.path.dirname(options.target_image) |
| 271 for key_manager in au_test.AUTest.public_key_managers: |
| 272 if key_manager.image_path.startswith(target_directory): |
| 273 key_manager.RemoveKeyFromImage() |
300 | 274 |
301 | 275 |
302 if __name__ == '__main__': | 276 if __name__ == '__main__': |
303 main() | 277 main() |
OLD | NEW |