OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 """Module that contains the interface for au_test_harness workers. |
| 6 |
| 7 An au test harnss worker is a class that contains the logic for performing |
| 8 and validating updates on a target. This should be subclassed to handle |
| 9 various types of target. Types of targets include VM's, real devices, etc. |
| 10 """ |
| 11 |
| 12 import os |
| 13 import sys |
| 14 |
| 15 import cros_build_lib as cros_lib |
| 16 |
| 17 import dev_server_wrapper |
| 18 import update_exception |
| 19 |
| 20 |
| 21 class AUWorker(object): |
| 22 """Interface for a worker that updates and verifies images.""" |
| 23 |
| 24 update_cache = None |
| 25 |
| 26 # --- INTERFACE --- |
| 27 |
| 28 def __init__(self, options): |
| 29 """Processes options for the specific-type of worker.""" |
| 30 self.board = options.board |
| 31 self.private_key = options.private_key |
| 32 self.use_delta_updates = options.delta |
| 33 self.verbose = options.verbose |
| 34 self.vm_image_path = None |
| 35 if options.quick_test: |
| 36 self.verify_suite = 'build_RootFilesystemSize' |
| 37 else: |
| 38 self.verify_suite = 'suite_Smoke' |
| 39 |
| 40 # Set these up as they are used often. |
| 41 self.crosutils = os.path.join(os.path.dirname(__file__), '..', '..') |
| 42 self.crosutilsbin = os.path.join(os.path.dirname(__file__), '..') |
| 43 |
| 44 def CleanUp(self): |
| 45 """Called at the end of every test.""" |
| 46 pass |
| 47 |
| 48 def UpdateImage(self, image_path, src_image_path='', stateful_change='old', |
| 49 proxy_port=None, private_key_path=None): |
| 50 """Implementation of an actual update. |
| 51 |
| 52 See PerformUpdate for description of args. Subclasses must override this |
| 53 method with the correct update procedure for the class. |
| 54 """ |
| 55 pass |
| 56 |
| 57 def UpdateUsingPayload(self, update_path, stateful_change='old', |
| 58 proxy_port=None): |
| 59 """Updates target with the pre-generated update stored in update_path. |
| 60 |
| 61 Subclasses must override this method with the correct update procedure for |
| 62 the class. |
| 63 |
| 64 Args: |
| 65 update_path: Path to the image to update with. This directory should |
| 66 contain both update.gz, and stateful.image.gz |
| 67 proxy_port: Port to have the client connect to. For use with |
| 68 CrosTestProxy. |
| 69 """ |
| 70 pass |
| 71 |
| 72 def VerifyImage(self, unittest, percent_required_to_pass=100): |
| 73 """Verifies the image with tests. |
| 74 |
| 75 Verifies that the test images passes the percent required. Subclasses must |
| 76 override this method with the correct update procedure for the class. |
| 77 |
| 78 Args: |
| 79 unittest: pointer to a unittest to fail if we cannot verify the image. |
| 80 percent_required_to_pass: percentage required to pass. This should be |
| 81 fall between 0-100. |
| 82 |
| 83 Returns: |
| 84 Returns the percent that passed. |
| 85 """ |
| 86 pass |
| 87 |
| 88 # --- INTERFACE TO AU_TEST --- |
| 89 |
| 90 def PerformUpdate(self, image_path, src_image_path='', stateful_change='old', |
| 91 proxy_port=None, private_key_path=None): |
| 92 """Performs an update using _UpdateImage and reports any error. |
| 93 |
| 94 Subclasses should not override this method but override _UpdateImage |
| 95 instead. |
| 96 |
| 97 Args: |
| 98 image_path: Path to the image to update with. This image must be a test |
| 99 image. |
| 100 src_image_path: Optional. If set, perform a delta update using the |
| 101 image specified by the path as the source image. |
| 102 stateful_change: How to modify the stateful partition. Values are: |
| 103 'old': Don't modify stateful partition. Just update normally. |
| 104 'clean': Uses clobber-state to wipe the stateful partition with the |
| 105 exception of code needed for ssh. |
| 106 proxy_port: Port to have the client connect to. For use with |
| 107 CrosTestProxy. |
| 108 private_key_path: Path to a private key to use with update payload. |
| 109 Raises an update_exception.UpdateException if _UpdateImage returns an error. |
| 110 """ |
| 111 try: |
| 112 if not self.use_delta_updates: src_image_path = '' |
| 113 if private_key_path: |
| 114 key_to_use = private_key_path |
| 115 else: |
| 116 key_to_use = self.private_key |
| 117 |
| 118 self.UpdateImage(image_path, src_image_path, stateful_change, |
| 119 proxy_port, key_to_use) |
| 120 except update_exception.UpdateException as err: |
| 121 # If the update fails, print it out |
| 122 Warning(err.stdout) |
| 123 raise |
| 124 |
| 125 @classmethod |
| 126 def SetUpdateCache(cls, update_cache): |
| 127 """Sets the global update cache for getting paths to devserver payloads.""" |
| 128 cls.update_cache = update_cache |
| 129 |
| 130 # --- METHODS FOR SUB CLASS USE --- |
| 131 |
| 132 def PrepareRealBase(self, image_path): |
| 133 """Prepares a remote device for worker test by updating it to the image.""" |
| 134 self.UpdateImage(image_path) |
| 135 |
| 136 def PrepareVMBase(self, image_path): |
| 137 """Prepares a VM image for worker test by creating the VM file from the img. |
| 138 """ |
| 139 # VM Constants. |
| 140 FULL_VDISK_SIZE = 6072 |
| 141 FULL_STATEFULFS_SIZE = 3074 |
| 142 # Needed for VM delta updates. We need to use the qemu image rather |
| 143 # than the base image on a first update. By tracking the first_update |
| 144 # we can set src_image to the qemu form of the base image when |
| 145 # performing generating the delta payload. |
| 146 self._first_update = True |
| 147 self.vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname( |
| 148 image_path) |
| 149 if not os.path.exists(self.vm_image_path): |
| 150 cros_lib.Info('Creating %s' % self.vm_image_path) |
| 151 cros_lib.RunCommand(['./image_to_vm.sh', |
| 152 '--full', |
| 153 '--from=%s' % cros_lib.ReinterpretPathForChroot( |
| 154 os.path.dirname(image_path)), |
| 155 '--vdisk_size=%s' % FULL_VDISK_SIZE, |
| 156 '--statefulfs_size=%s' % FULL_STATEFULFS_SIZE, |
| 157 '--board=%s' % self.board, |
| 158 '--test_image' |
| 159 ], enter_chroot=True, cwd=self.crosutils) |
| 160 |
| 161 cros_lib.Info('Using %s as base' % self.vm_image_path) |
| 162 assert os.path.exists(self.vm_image_path) |
| 163 |
| 164 def GetStatefulChangeFlag(self, stateful_change): |
| 165 """Returns the flag to pass to image_to_vm for the stateful change.""" |
| 166 stateful_change_flag = '' |
| 167 if stateful_change: |
| 168 stateful_change_flag = '--stateful_update_flag=%s' % stateful_change |
| 169 |
| 170 return stateful_change_flag |
| 171 |
| 172 def AppendUpdateFlags(self, cmd, image_path, src_image_path, proxy_port, |
| 173 private_key_path): |
| 174 """Appends common args to an update cmd defined by an array. |
| 175 |
| 176 Modifies cmd in places by appending appropriate items given args. |
| 177 """ |
| 178 if proxy_port: cmd.append('--proxy_port=%s' % proxy_port) |
| 179 |
| 180 # Get pregenerated update if we have one. |
| 181 update_id = dev_server_wrapper.GenerateUpdateId(image_path, src_image_path, |
| 182 private_key_path) |
| 183 cache_path = self.update_cache[update_id] |
| 184 if cache_path: |
| 185 update_url = dev_server_wrapper.DevServerWrapper.GetDevServerURL( |
| 186 proxy_port, cache_path) |
| 187 cmd.append('--update_url=%s' % update_url) |
| 188 else: |
| 189 cmd.append('--image=%s' % image_path) |
| 190 if src_image_path: cmd.append('--src_image=%s' % src_image_path) |
| 191 |
| 192 def RunUpdateCmd(self, cmd): |
| 193 """Runs the given update cmd given verbose options. |
| 194 |
| 195 Raises an update_exception.UpdateException if the update fails. |
| 196 """ |
| 197 if self.verbose: |
| 198 try: |
| 199 cros_lib.RunCommand(cmd) |
| 200 except Exception as e: |
| 201 Warning(str(e)) |
| 202 raise update_exception.UpdateException(1, str(e)) |
| 203 else: |
| 204 (code, stdout, stderr) = cros_lib.RunCommandCaptureOutput(cmd) |
| 205 if code != 0: |
| 206 Warning(stdout) |
| 207 raise update_exception.UpdateException(code, stdout) |
| 208 |
| 209 def AssertEnoughTestsPassed(self, unittest, output, percent_required_to_pass): |
| 210 """Helper function that asserts a sufficient number of tests passed. |
| 211 |
| 212 Args: |
| 213 output: stdout from a test run. |
| 214 percent_required_to_pass: percentage required to pass. This should be |
| 215 fall between 0-100. |
| 216 Returns: |
| 217 percent that passed. |
| 218 """ |
| 219 cros_lib.Info('Output from VerifyImage():') |
| 220 print >> sys.stderr, output |
| 221 sys.stderr.flush() |
| 222 percent_passed = self._ParseGenerateTestReportOutput(output) |
| 223 cros_lib.Info('Percent passed: %d vs. Percent required: %d' % ( |
| 224 percent_passed, percent_required_to_pass)) |
| 225 unittest.assertTrue(percent_passed >= percent_required_to_pass) |
| 226 return percent_passed |
| 227 |
| 228 # --- PRIVATE HELPER FUNCTIONS --- |
| 229 |
| 230 def _ParseGenerateTestReportOutput(self, output): |
| 231 """Returns the percentage of tests that passed based on output.""" |
| 232 percent_passed = 0 |
| 233 lines = output.split('\n') |
| 234 |
| 235 for line in lines: |
| 236 if line.startswith("Total PASS:"): |
| 237 # FORMAT: ^TOTAL PASS: num_passed/num_total (percent%)$ |
| 238 percent_passed = line.split()[3].strip('()%') |
| 239 cros_lib.Info('Percent of tests passed %s' % percent_passed) |
| 240 break |
| 241 |
| 242 return int(percent_passed) |
OLD | NEW |