Chromium Code Reviews| Index: bin/au_test_harness/au_worker.py |
| diff --git a/bin/au_test_harness/au_worker.py b/bin/au_test_harness/au_worker.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..c8fbde152c87866ce723cfa8e41c157251308ed7 |
| --- /dev/null |
| +++ b/bin/au_test_harness/au_worker.py |
| @@ -0,0 +1,238 @@ |
| +# Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| + |
| +"""Module that contains the interface for au_test_harness workers. |
| + |
| +An au test harnss worker is a class that contains the logic for performing |
| +and validating updates on a target. This should be subclassed to handle |
| +various types of target. Types of targets include VM's, real devices, etc. |
| +""" |
| + |
| +import os |
| +import sys |
| + |
| +import cros_build_lib as cros_lib |
| + |
| +import dev_server_wrapper |
| +import update_exception |
| + |
| + |
| +class AUWorker(object): |
| + """Interface for a worker that updates and verifies images.""" |
| + |
| + update_cache = None |
| + |
| + # --- INTERFACE --- |
| + |
| + def __init__(self, options): |
| + """Processes options for the specific-type of worker.""" |
| + self.board = options.board |
| + self.private_key = options.private_key |
| + self.use_delta_updates = options.delta |
| + self.verbose = options.verbose |
| + self.vm_image_path = None |
| + if options.quick_test: |
| + self.verify_suite = 'build_RootFilesystemSize' |
| + else: |
| + self.verify_suite = 'suite_Smoke' |
| + |
| + # Set these up as they are used often. |
| + self.crosutils = os.path.join(os.path.dirname(__file__), '..', '..') |
| + self.crosutilsbin = os.path.join(os.path.dirname(__file__), '..') |
| + |
| + def UpdateImage(self, image_path, src_image_path='', stateful_change='old', |
| + proxy_port=None, private_key_path=None): |
| + """Implementation of an actual update. |
| + |
| + See PerformUpdate for description of args. Subclasses must override this |
| + method with the correct update procedure for the class. |
| + """ |
| + pass |
| + |
| + def UpdateUsingPayload(self, update_path, stateful_change='old', |
| + proxy_port=None): |
| + """Updates target with the pre-generated update stored in update_path. |
| + |
| + Subclasses must override this method with the correct update procedure for |
| + the class. |
| + |
| + Args: |
| + update_path: Path to the image to update with. This directory should |
| + contain both update.gz, and stateful.image.gz |
| + proxy_port: Port to have the client connect to. For use with |
| + CrosTestProxy. |
| + """ |
| + pass |
| + |
| + def VerifyImage(self, unittest, percent_required_to_pass=100): |
| + """Verifies the image with tests. |
| + |
| + Verifies that the test images passes the percent required. Subclasses must |
| + override this method with the correct update procedure for the class. |
| + |
| + Args: |
| + unittest: pointer to a unittest to fail if we cannot verify the image. |
| + percent_required_to_pass: percentage required to pass. This should be |
| + fall between 0-100. |
| + |
| + Returns: |
| + Returns the percent that passed. |
| + """ |
| + pass |
| + |
| + # --- INTERFACE TO AU_TEST --- |
| + |
| + def PerformUpdate(self, image_path, src_image_path='', stateful_change='old', |
| + proxy_port=None, private_key_path=None): |
| + """Performs an update using _UpdateImage and reports any error. |
| + |
| + Subclasses should not override this method but override _UpdateImage |
| + instead. |
| + |
| + Args: |
| + image_path: Path to the image to update with. This image must be a test |
| + image. |
| + src_image_path: Optional. If set, perform a delta update using the |
| + image specified by the path as the source image. |
| + stateful_change: How to modify the stateful partition. Values are: |
| + 'old': Don't modify stateful partition. Just update normally. |
| + 'clean': Uses clobber-state to wipe the stateful partition with the |
| + exception of code needed for ssh. |
| + proxy_port: Port to have the client connect to. For use with |
| + CrosTestProxy. |
| + private_key_path: Path to a private key to use with update payload. |
| + Raises an update_exception.UpdateException if _UpdateImage returns an error. |
| + """ |
| + try: |
| + if not self.use_delta_updates: src_image_path = '' |
| + if private_key_path: |
| + key_to_use = private_key_path |
| + else: |
| + key_to_use = self.private_key |
| + |
| + self.UpdateImage(image_path, src_image_path, stateful_change, |
| + proxy_port, key_to_use) |
| + except update_exception.UpdateException as err: |
| + # If the update fails, print it out |
| + Warning(err.stdout) |
| + raise |
| + |
| + @classmethod |
|
dgarrett
2011/03/03 02:17:21
Why classmethod here?
sosa
2011/03/03 03:11:41
Sets a static variable. I'm using class variables
|
| + def SetUpdateCache(cls, update_cache): |
| + """Sets the global update cache for getting paths to devserver payloads.""" |
| + cls.update_cache = update_cache |
| + |
| + # --- METHODS FOR SUB CLASS USE --- |
| + |
| + def PrepareRealBase(self, image_path): |
| + """Prepares a remote device for worker test by updating it to the image.""" |
| + self.UpdateImage(image_path) |
| + |
| + def PrepareVMBase(self, image_path): |
| + """Prepares a VM image for worker test by creating the VM file from the img. |
| + """ |
| + # VM Constants. |
| + FULL_VDISK_SIZE = 6072 |
| + FULL_STATEFULFS_SIZE = 3074 |
| + # Needed for VM delta updates. We need to use the qemu image rather |
| + # than the base image on a first update. By tracking the first_update |
| + # we can set src_image to the qemu form of the base image when |
| + # performing generating the delta payload. |
| + self._first_update = True |
| + self.vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname( |
| + image_path) |
| + if not os.path.exists(self.vm_image_path): |
| + cros_lib.Info('Creating %s' % self.vm_image_path) |
| + cros_lib.RunCommand(['./image_to_vm.sh', |
| + '--full', |
| + '--from=%s' % cros_lib.ReinterpretPathForChroot( |
| + os.path.dirname(image_path)), |
| + '--vdisk_size=%s' % FULL_VDISK_SIZE, |
| + '--statefulfs_size=%s' % FULL_STATEFULFS_SIZE, |
| + '--board=%s' % self.board, |
| + '--test_image' |
| + ], enter_chroot=True, cwd=self.crosutils) |
| + |
| + cros_lib.Info('Using %s as base' % self.vm_image_path) |
| + assert os.path.exists(self.vm_image_path) |
| + |
| + def GetStatefulChangeFlag(self, stateful_change): |
| + """Returns the flag to pass to image_to_vm for the stateful change.""" |
| + stateful_change_flag = '' |
| + if stateful_change: |
| + stateful_change_flag = '--stateful_update_flag=%s' % stateful_change |
| + |
| + return stateful_change_flag |
| + |
| + def AppendUpdateFlags(self, cmd, image_path, src_image_path, proxy_port, |
| + private_key_path): |
| + """Appends common args to an update cmd defined by an array. |
| + |
| + Modifies cmd in places by appending appropriate items given args. |
| + """ |
| + if proxy_port: cmd.append('--proxy_port=%s' % proxy_port) |
| + |
| + # Get pregenerated update if we have one. |
| + update_id = dev_server_wrapper.GenerateUpdateId(image_path, src_image_path, |
| + private_key_path) |
| + cache_path = self.update_cache[update_id] |
| + if cache_path: |
| + update_url = dev_server_wrapper.DevServerWrapper.GetDevServerURL( |
| + proxy_port, cache_path) |
| + cmd.append('--update_url=%s' % update_url) |
| + else: |
| + cmd.append('--image=%s' % image_path) |
| + if src_image_path: cmd.append('--src_image=%s' % src_image_path) |
| + |
| + def RunUpdateCmd(self, cmd): |
| + """Runs the given update cmd given verbose options. |
| + |
| + Raises an update_exception.UpdateException if the update fails. |
| + """ |
| + if self.verbose: |
| + try: |
| + cros_lib.RunCommand(cmd) |
| + except Exception, e: |
|
dgarrett
2011/03/03 02:17:21
This syntax is morphing. What you are doing works,
sosa
2011/03/03 03:11:41
Done.
|
| + Warning(str(e)) |
| + raise update_exception.UpdateException(1, str(e)) |
| + else: |
| + (code, stdout, stderr) = cros_lib.RunCommandCaptureOutput(cmd) |
| + if code != 0: |
| + Warning(stdout) |
| + raise update_exception.UpdateException(code, stdout) |
| + |
| + def AssertEnoughTestsPassed(self, unittest, output, percent_required_to_pass): |
| + """Helper function that asserts a sufficient number of tests passed. |
| + |
| + Args: |
| + output: stdout from a test run. |
| + percent_required_to_pass: percentage required to pass. This should be |
| + fall between 0-100. |
| + Returns: |
| + percent that passed. |
| + """ |
| + cros_lib.Info('Output from VerifyImage():') |
| + print >> sys.stderr, output |
| + sys.stderr.flush() |
| + percent_passed = self._ParseGenerateTestReportOutput(output) |
| + cros_lib.Info('Percent passed: %d vs. Percent required: %d' % ( |
| + percent_passed, percent_required_to_pass)) |
| + unittest.assertTrue(percent_passed >= percent_required_to_pass) |
| + return percent_passed |
| + |
| + # --- PRIVATE HELPER FUNCTIONS --- |
| + |
| + def _ParseGenerateTestReportOutput(self, output): |
| + """Returns the percentage of tests that passed based on output.""" |
| + percent_passed = 0 |
| + lines = output.split('\n') |
| + |
| + for line in lines: |
| + if line.startswith("Total PASS:"): |
| + # FORMAT: ^TOTAL PASS: num_passed/num_total (percent%)$ |
| + percent_passed = line.split()[3].strip('()%') |
| + cros_lib.Info('Percent of tests passed %s' % percent_passed) |
| + break |
| + |
| + return int(percent_passed) |