| 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 |