| OLD | NEW |
| 1 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. | 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 | 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 """Module that contains the interface for au_test_harness workers. | 5 """Module that contains the interface for au_test_harness workers. |
| 6 | 6 |
| 7 An au test harnss worker is a class that contains the logic for performing | 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 | 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. | 9 various types of target. Types of targets include VM's, real devices, etc. |
| 10 """ | 10 """ |
| (...skipping 30 matching lines...) Expand all Loading... |
| 41 self.verify_suite = 'suite_Smoke' | 41 self.verify_suite = 'suite_Smoke' |
| 42 | 42 |
| 43 # Set these up as they are used often. | 43 # Set these up as they are used often. |
| 44 self.crosutils = cros_lib.GetCrosUtilsPath() | 44 self.crosutils = cros_lib.GetCrosUtilsPath() |
| 45 self.crosutilsbin = cros_lib.GetCrosUtilsBinPath() | 45 self.crosutilsbin = cros_lib.GetCrosUtilsBinPath() |
| 46 | 46 |
| 47 def CleanUp(self): | 47 def CleanUp(self): |
| 48 """Called at the end of every test.""" | 48 """Called at the end of every test.""" |
| 49 pass | 49 pass |
| 50 | 50 |
| 51 def GetUpdateMessage(self, update_target, update_base, from_vm, proxy): |
| 52 if update_base: |
| 53 str = 'Performing a delta update from %s to %s' % ( |
| 54 update_base, update_target) |
| 55 else: |
| 56 str = 'Performing a full update to %s' % update_target |
| 57 |
| 58 if from_vm: str += ' in a VM' |
| 59 if proxy: ' using a proxy on port %s' % proxy |
| 60 return str |
| 61 |
| 51 def UpdateImage(self, image_path, src_image_path='', stateful_change='old', | 62 def UpdateImage(self, image_path, src_image_path='', stateful_change='old', |
| 52 proxy_port=None, private_key_path=None): | 63 proxy_port=None, private_key_path=None): |
| 53 """Implementation of an actual update. | 64 """Implementation of an actual update. |
| 54 | 65 |
| 55 See PerformUpdate for description of args. Subclasses must override this | 66 See PerformUpdate for description of args. Subclasses must override this |
| 56 method with the correct update procedure for the class. | 67 method with the correct update procedure for the class. |
| 57 """ | 68 """ |
| 58 pass | 69 pass |
| 59 | 70 |
| 60 def UpdateUsingPayload(self, update_path, stateful_change='old', | 71 def UpdateUsingPayload(self, update_path, stateful_change='old', |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 104 image specified by the path as the source image. | 115 image specified by the path as the source image. |
| 105 stateful_change: How to modify the stateful partition. Values are: | 116 stateful_change: How to modify the stateful partition. Values are: |
| 106 'old': Don't modify stateful partition. Just update normally. | 117 'old': Don't modify stateful partition. Just update normally. |
| 107 'clean': Uses clobber-state to wipe the stateful partition with the | 118 'clean': Uses clobber-state to wipe the stateful partition with the |
| 108 exception of code needed for ssh. | 119 exception of code needed for ssh. |
| 109 proxy_port: Port to have the client connect to. For use with | 120 proxy_port: Port to have the client connect to. For use with |
| 110 CrosTestProxy. | 121 CrosTestProxy. |
| 111 private_key_path: Path to a private key to use with update payload. | 122 private_key_path: Path to a private key to use with update payload. |
| 112 Raises an update_exception.UpdateException if _UpdateImage returns an error. | 123 Raises an update_exception.UpdateException if _UpdateImage returns an error. |
| 113 """ | 124 """ |
| 114 try: | 125 if not self.use_delta_updates: src_image_path = '' |
| 115 if not self.use_delta_updates: src_image_path = '' | 126 if private_key_path: |
| 116 if private_key_path: | 127 key_to_use = private_key_path |
| 117 key_to_use = private_key_path | 128 else: |
| 118 else: | 129 key_to_use = self.private_key |
| 119 key_to_use = self.private_key | |
| 120 | 130 |
| 121 self.UpdateImage(image_path, src_image_path, stateful_change, | 131 self.UpdateImage(image_path, src_image_path, stateful_change, |
| 122 proxy_port, key_to_use) | 132 proxy_port, key_to_use) |
| 123 except update_exception.UpdateException as err: | |
| 124 # If the update fails, print it out | |
| 125 Warning(err.stdout) | |
| 126 raise | |
| 127 | 133 |
| 128 @classmethod | 134 @classmethod |
| 129 def SetUpdateCache(cls, update_cache): | 135 def SetUpdateCache(cls, update_cache): |
| 130 """Sets the global update cache for getting paths to devserver payloads.""" | 136 """Sets the global update cache for getting paths to devserver payloads.""" |
| 131 cls.update_cache = update_cache | 137 cls.update_cache = update_cache |
| 132 | 138 |
| 133 # --- METHODS FOR SUB CLASS USE --- | 139 # --- METHODS FOR SUB CLASS USE --- |
| 134 | 140 |
| 135 def PrepareRealBase(self, image_path): | 141 def PrepareRealBase(self, image_path): |
| 136 """Prepares a remote device for worker test by updating it to the image.""" | 142 """Prepares a remote device for worker test by updating it to the image.""" |
| (...skipping 17 matching lines...) Expand all Loading... |
| 154 cros_lib.RunCommand(['./image_to_vm.sh', | 160 cros_lib.RunCommand(['./image_to_vm.sh', |
| 155 '--full', | 161 '--full', |
| 156 '--from=%s' % cros_lib.ReinterpretPathForChroot( | 162 '--from=%s' % cros_lib.ReinterpretPathForChroot( |
| 157 os.path.dirname(image_path)), | 163 os.path.dirname(image_path)), |
| 158 '--vdisk_size=%s' % FULL_VDISK_SIZE, | 164 '--vdisk_size=%s' % FULL_VDISK_SIZE, |
| 159 '--statefulfs_size=%s' % FULL_STATEFULFS_SIZE, | 165 '--statefulfs_size=%s' % FULL_STATEFULFS_SIZE, |
| 160 '--board=%s' % self.board, | 166 '--board=%s' % self.board, |
| 161 '--test_image' | 167 '--test_image' |
| 162 ], enter_chroot=True, cwd=self.crosutils) | 168 ], enter_chroot=True, cwd=self.crosutils) |
| 163 | 169 |
| 164 cros_lib.Info('Using %s as base' % self.vm_image_path) | |
| 165 assert os.path.exists(self.vm_image_path) | 170 assert os.path.exists(self.vm_image_path) |
| 166 | 171 |
| 167 def GetStatefulChangeFlag(self, stateful_change): | 172 def GetStatefulChangeFlag(self, stateful_change): |
| 168 """Returns the flag to pass to image_to_vm for the stateful change.""" | 173 """Returns the flag to pass to image_to_vm for the stateful change.""" |
| 169 stateful_change_flag = '' | 174 stateful_change_flag = '' |
| 170 if stateful_change: | 175 if stateful_change: |
| 171 stateful_change_flag = '--stateful_update_flag=%s' % stateful_change | 176 stateful_change_flag = '--stateful_update_flag=%s' % stateful_change |
| 172 | 177 |
| 173 return stateful_change_flag | 178 return stateful_change_flag |
| 174 | 179 |
| 175 def AppendUpdateFlags(self, cmd, image_path, src_image_path, proxy_port, | 180 def AppendUpdateFlags(self, cmd, image_path, src_image_path, proxy_port, |
| 176 private_key_path): | 181 private_key_path): |
| 177 """Appends common args to an update cmd defined by an array. | 182 """Appends common args to an update cmd defined by an array. |
| 178 | 183 |
| 179 Modifies cmd in places by appending appropriate items given args. | 184 Modifies cmd in places by appending appropriate items given args. |
| 180 """ | 185 """ |
| 181 if proxy_port: cmd.append('--proxy_port=%s' % proxy_port) | 186 if proxy_port: cmd.append('--proxy_port=%s' % proxy_port) |
| 182 | |
| 183 # Get pregenerated update if we have one. | 187 # Get pregenerated update if we have one. |
| 184 update_id = dev_server_wrapper.GenerateUpdateId(image_path, src_image_path, | 188 update_id = dev_server_wrapper.GenerateUpdateId(image_path, src_image_path, |
| 185 private_key_path) | 189 private_key_path) |
| 186 cache_path = self.update_cache[update_id] | 190 cache_path = self.update_cache[update_id] |
| 187 if cache_path: | 191 if cache_path: |
| 188 update_url = dev_server_wrapper.DevServerWrapper.GetDevServerURL( | 192 update_url = dev_server_wrapper.DevServerWrapper.GetDevServerURL( |
| 189 proxy_port, cache_path) | 193 proxy_port, cache_path) |
| 190 cmd.append('--update_url=%s' % update_url) | 194 cmd.append('--update_url=%s' % update_url) |
| 191 else: | 195 else: |
| 192 cmd.append('--image=%s' % image_path) | 196 cmd.append('--image=%s' % image_path) |
| 193 if src_image_path: cmd.append('--src_image=%s' % src_image_path) | 197 if src_image_path: cmd.append('--src_image=%s' % src_image_path) |
| 194 | 198 |
| 195 def RunUpdateCmd(self, cmd, log_directory=None): | 199 def RunUpdateCmd(self, cmd, log_directory=None): |
| 196 """Runs the given update cmd given verbose options. | 200 """Runs the given update cmd given verbose options. |
| 197 | 201 |
| 198 Raises an update_exception.UpdateException if the update fails. | 202 Raises an update_exception.UpdateException if the update fails. |
| 199 """ | 203 """ |
| 200 if self.verbose: | 204 if self.verbose: |
| 201 try: | 205 try: |
| 202 if log_directory: | 206 if log_directory: |
| 203 cros_lib.RunCommand(cmd, log_to_file=os.path.join(log_directory, | 207 cros_lib.RunCommand( |
| 204 'update.log')) | 208 cmd, log_to_file=os.path.join(log_directory, 'update.log'), |
| 209 print_cmd=False) |
| 205 else: | 210 else: |
| 206 cros_lib.RunCommand(cmd) | 211 cros_lib.RunCommand(cmd, print_cmd=False) |
| 207 except Exception as e: | 212 except cros_lib.RunCommandException as e: |
| 208 Warning(str(e)) | |
| 209 raise update_exception.UpdateException(1, str(e)) | 213 raise update_exception.UpdateException(1, str(e)) |
| 210 else: | 214 else: |
| 211 (code, stdout, stderr) = cros_lib.RunCommandCaptureOutput(cmd) | 215 (code, stdout, unused_stderr) = cros_lib.RunCommandCaptureOutput( |
| 216 cmd, print_cmd=False) |
| 212 if code != 0: | 217 if code != 0: |
| 213 Warning(stdout) | 218 cros_lib.Warning(stdout) |
| 214 raise update_exception.UpdateException(code, stdout) | 219 raise update_exception.UpdateException(code, stdout) |
| 215 | 220 |
| 216 def AssertEnoughTestsPassed(self, unittest, output, percent_required_to_pass): | 221 def AssertEnoughTestsPassed(self, unittest, output, percent_required_to_pass): |
| 217 """Helper function that asserts a sufficient number of tests passed. | 222 """Helper function that asserts a sufficient number of tests passed. |
| 218 | 223 |
| 219 Args: | 224 Args: |
| 220 output: stdout from a test run. | 225 output: stdout from a test run. |
| 221 percent_required_to_pass: percentage required to pass. This should be | 226 percent_required_to_pass: percentage required to pass. This should be |
| 222 fall between 0-100. | 227 fall between 0-100. |
| 223 Returns: | 228 Returns: |
| 224 percent that passed. | 229 percent that passed. |
| 225 """ | 230 """ |
| 226 cros_lib.Info('Output from VerifyImage():') | |
| 227 print >> sys.stderr, output | |
| 228 sys.stderr.flush() | |
| 229 percent_passed = self._ParseGenerateTestReportOutput(output) | 231 percent_passed = self._ParseGenerateTestReportOutput(output) |
| 230 cros_lib.Info('Percent passed: %d vs. Percent required: %d' % ( | 232 self.TestInfo('Percent passed: %d vs. Percent required: %d' % ( |
| 231 percent_passed, percent_required_to_pass)) | 233 percent_passed, percent_required_to_pass)) |
| 232 unittest.assertTrue(percent_passed >= percent_required_to_pass) | 234 if percent_passed < percent_required_to_pass: |
| 235 print output |
| 236 unittest.fail('%d percent of tests are required to pass' % |
| 237 percent_required_to_pass) |
| 238 |
| 233 return percent_passed | 239 return percent_passed |
| 234 | 240 |
| 241 def TestInfo(self, message): |
| 242 cros_lib.Info('%s: %s' % (self.test_name, message)) |
| 243 |
| 235 def Initialize(self, port): | 244 def Initialize(self, port): |
| 236 """Initializes test specific variables for each test. | 245 """Initializes test specific variables for each test. |
| 237 | 246 |
| 238 Each test needs to specify a unique ssh port. | 247 Each test needs to specify a unique ssh port. |
| 239 | 248 |
| 240 Args: | 249 Args: |
| 241 port: Unique port for ssh access. | 250 port: Unique port for ssh access. |
| 242 """ | 251 """ |
| 243 # Initialize port vars. | 252 # Initialize port vars. |
| 244 self._ssh_port = port | 253 self._ssh_port = port |
| 245 self._kvm_pid_file = '/tmp/kvm.%d' % port | 254 self._kvm_pid_file = '/tmp/kvm.%d' % port |
| 246 | 255 |
| 247 # Initialize test results directory. | 256 # Initialize test results directory. |
| 248 test_name = inspect.stack()[1][3] | 257 self.test_name = inspect.stack()[1][3] |
| 249 self.results_directory = os.path.join(self.test_results_root, test_name) | 258 self.results_directory = os.path.join(self.test_results_root, |
| 259 self.test_name) |
| 250 self.results_count = 0 | 260 self.results_count = 0 |
| 251 | 261 |
| 252 def GetNextResultsPath(self, label): | 262 def GetNextResultsPath(self, label): |
| 253 """Returns a path for the results directory for this label. | 263 """Returns a path for the results directory for this label. |
| 254 | 264 |
| 255 Prefixes directory returned for worker with time called i.e. 1_label, | 265 Prefixes directory returned for worker with time called i.e. 1_label, |
| 256 2_label, etc. The directory returned is outside the chroot so if passing | 266 2_label, etc. The directory returned is outside the chroot so if passing |
| 257 to an script that is called with enther_chroot, make sure to use | 267 to an script that is called with enther_chroot, make sure to use |
| 258 ReinterpretPathForChroot. | 268 ReinterpretPathForChroot. |
| 259 """ | 269 """ |
| 260 self.results_count += 1 | 270 self.results_count += 1 |
| 261 dir = os.path.join(self.results_directory, '%s_%s' % (self.results_count, | 271 dir = os.path.join(self.results_directory, '%s_%s' % (self.results_count, |
| 262 label)) | 272 label)) |
| 263 if not os.path.exists(dir): | 273 if not os.path.exists(dir): |
| 264 os.makedirs(dir) | 274 os.makedirs(dir) |
| 265 | 275 |
| 266 return dir | 276 return dir |
| 267 | 277 |
| 268 # --- PRIVATE HELPER FUNCTIONS --- | 278 # --- PRIVATE HELPER FUNCTIONS --- |
| 269 | 279 |
| 270 def _ParseGenerateTestReportOutput(self, output): | 280 def _ParseGenerateTestReportOutput(self, output): |
| 271 """Returns the percentage of tests that passed based on output.""" | 281 """Returns the percentage of tests that passed based on output.""" |
| 272 percent_passed = 0 | 282 percent_passed = 0 |
| 273 lines = output.split('\n') | 283 lines = output.split('\n') |
| 274 | 284 |
| 275 for line in lines: | 285 for line in lines: |
| 276 if line.startswith("Total PASS:"): | 286 if line.startswith("Total PASS:"): |
| 277 # FORMAT: ^TOTAL PASS: num_passed/num_total (percent%)$ | 287 # FORMAT: ^TOTAL PASS: num_passed/num_total (percent%)$ |
| 278 percent_passed = line.split()[3].strip('()%') | 288 percent_passed = line.split()[3].strip('()%') |
| 279 cros_lib.Info('Percent of tests passed %s' % percent_passed) | |
| 280 break | 289 break |
| 281 | 290 |
| 282 return int(percent_passed) | 291 return int(percent_passed) |
| OLD | NEW |