Chromium Code Reviews| Index: bin/cros_au_test_harness.py |
| diff --git a/bin/cros_au_test_harness.py b/bin/cros_au_test_harness.py |
| index f274b14e1af9e2abcb4235fb629900a7ad688e78..4b9c1d8d9ec3ec162aff2578cb36a7a90b7dd6b1 100755 |
| --- a/bin/cros_au_test_harness.py |
| +++ b/bin/cros_au_test_harness.py |
| @@ -16,7 +16,7 @@ import optparse |
| import os |
| import re |
| import sys |
| -import thread |
| +import threading |
| import time |
| import unittest |
| import urllib |
| @@ -134,9 +134,10 @@ class AUTest(object): |
| # Will raise ValueError if expected is not found. |
| if re.search(re.escape(expected_msg), err.stdout, re.MULTILINE): |
| return |
| + except Exception as err: |
|
dgarrett
2011/01/20 03:34:27
After reading this again, the new exception handle
sosa
2011/01/20 03:59:49
Done.
|
| + Warning("Didn't find '%s' in:" % expected_msg) |
| + Warning(err.stdout) |
| - Warning("Didn't find '%s' in:" % expected_msg) |
| - Warning(err.stdout) |
| self.fail('We managed to update when failure was expected') |
| def AttemptUpdateWithFilter(self, filter, proxy_port=8081): |
| @@ -155,7 +156,7 @@ class AUTest(object): |
| # This update is expected to fail... |
| try: |
| - self.PerformUpdate(self.target_image_path, self.base_image_path, |
| + self.PerformUpdate(self.target_image_path, self.target_image_path, |
| proxy_port=proxy_port) |
| finally: |
| proxy.shutdown() |
| @@ -248,12 +249,10 @@ class AUTest(object): |
| percent_passed = self.VerifyImage(10) |
| # Update to - all tests should pass on new image. |
| - Info('Updating from base image on vm to target image.') |
| self.PerformUpdate(self.target_image_path, self.base_image_path) |
| self.VerifyImage(100) |
| # Update from - same percentage should pass that originally passed. |
| - Info('Updating from updated image on vm back to base image.') |
| self.PerformUpdate(self.base_image_path, self.target_image_path) |
| self.VerifyImage(percent_passed) |
| @@ -271,12 +270,10 @@ class AUTest(object): |
| percent_passed = self.VerifyImage(10) |
| # Update to - all tests should pass on new image. |
| - Info('Updating from base image on vm to target image and wiping stateful.') |
| self.PerformUpdate(self.target_image_path, self.base_image_path, 'clean') |
| self.VerifyImage(100) |
| # Update from - same percentage should pass that originally passed. |
| - Info('Updating from updated image back to base image and wiping stateful.') |
| self.PerformUpdate(self.base_image_path, self.target_image_path, 'clean') |
| self.VerifyImage(percent_passed) |
| @@ -508,33 +505,30 @@ class VirtualAUTest(unittest.TestCase, AUTest): |
| def PrepareBase(self, image_path): |
| """Creates an update-able VM based on base image.""" |
| + self._first_update = True |
|
dgarrett
2011/01/20 03:34:27
A comment explaining the purpose of _first_image w
sosa
2011/01/20 03:59:49
Done.
|
| self.vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname( |
| image_path) |
| - |
| - Info('Creating: %s' % self.vm_image_path) |
| - |
| if not os.path.exists(self.vm_image_path): |
| - Info('Qemu image %s not found, creating one.' % self.vm_image_path) |
| + Info('Creating %s' % vm_image_path) |
| RunCommand(['%s/image_to_vm.sh' % self.crosutils, |
| - '--full', |
| - '--from=%s' % ReinterpretPathForChroot( |
| - os.path.dirname(image_path)), |
| - '--vdisk_size=%s' % self._FULL_VDISK_SIZE, |
| - '--statefulfs_size=%s' % self._FULL_STATEFULFS_SIZE, |
| - '--board=%s' % self.board, |
| - '--test_image'], enter_chroot=True) |
| - else: |
| - Info('Using existing VM image %s' % self.vm_image_path) |
| - |
| - Info('Testing for %s' % self.vm_image_path) |
| + '--full', |
| + '--from=%s' % ReinterpretPathForChroot( |
| + os.path.dirname(image_path)), |
| + '--vdisk_size=%s' % self._FULL_VDISK_SIZE, |
| + '--statefulfs_size=%s' % self._FULL_STATEFULFS_SIZE, |
| + '--board=%s' % self.board, |
| + '--test_image'], enter_chroot=True) |
|
dgarrett
2011/01/20 03:34:27
arguments should be indented to match the open [.
sosa
2011/01/20 03:59:49
Done.
|
| + |
| + Info('Using %s as base' % self.vm_image_path) |
| self.assertTrue(os.path.exists(self.vm_image_path)) |
| def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', |
| proxy_port=None): |
| """Updates VM image with image_path.""" |
| stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) |
| - if src_image_path == self.base_image_path: |
| + if self._first_update: |
| src_image_path = self.vm_image_path |
| + self._first_update = False |
| cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, |
| '--update_image_path=%s' % image_path, |
| @@ -608,6 +602,139 @@ class VirtualAUTest(unittest.TestCase, AUTest): |
| return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) |
| +class GenerateVirtualAUDeltasTest(VirtualAUTest): |
| + """Class the overrides VirtualAUTest and stores deltas we will generate.""" |
| + delta_list = {} |
| + |
| + def setUp(self): |
| + AUTest.setUp(self) |
| + |
| + def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', |
| + proxy_port=None): |
| + if self._first_update: |
| + src_image_path = self.vm_image_path |
| + self._first_update = False |
| + |
| + image_path = ReinterpretPathForChroot(image_path) |
| + if src_image_path: |
| + src_image_path = ReinterpretPathForChroot(src_image_path) |
| + if not self.delta_list.has_key(image_path): |
| + self.delta_list[image_path] = set([src_image_path]) |
| + else: |
| + self.delta_list[image_path].add(src_image_path) |
| + |
| + def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg): |
| + pass |
| + |
| + def VerifyImage(self, percent_required_to_pass): |
| + pass |
| + |
| + |
| +class ParallelJob(threading.Thread): |
| + """Small wrapper for threading.Thread that releases a semaphore on exit.""" |
| + def __init__(self, semaphore, target, args): |
| + threading.Thread.__init__(self, target=target, args=args) |
| + self._target = target |
| + self._args = args |
| + self._semaphore = semaphore |
| + self._output = None |
| + self._completed = False |
| + |
| + def run(self): |
| + try: |
| + threading.Thread.run(self) |
| + finally: |
| + self._Cleanup() |
| + self._completed = True |
| + |
| + def GetOutput(self): |
| + assert self._completed, 'GetOutput called before thread was run.' |
| + return self._output |
| + |
| + def _Cleanup(self): |
| + self._semaphore.release() |
| + |
| + def __str__(self): |
| + return '%s(%s)' % (self._target, self._args) |
| + |
| + |
| +def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args): |
| + """Runs set number of specified jobs in parallel. |
| + |
| + Args: |
| + number_of_simultaneous_jobs: Max number of threads to be run in parallel. |
| + jobs: Array of methods to run. |
| + jobs_args: Array of args associated with method calls. |
| + Returns: |
| + Returns an array of results corresponding to each thread. |
| + """ |
| + def _TwoTupleize(x, y): |
| + return (x, y) |
| + |
| + threads = [] |
| + job_pool_semaphore = threading.Semaphore(number_of_sumultaneous_jobs) |
| + assert len(jobs) == len(jobs_args), 'Length of args array is wrong.' |
| + |
| + # Create the parallel jobs. |
| + for job, args in map(_TwoTupleize, jobs, jobs_args): |
| + thread = ParallelJob(job_pool_semaphore, target=job, args=args) |
| + threads.append(thread) |
| + |
| + # We use a semaphore to ensure we don't run more jobs that required. |
| + # After each thread finishes, it releases (increments semaphore). |
| + # Acquire blocks of num jobs reached and continues when a thread finishes. |
| + for next_thread in threads: |
| + job_pool_semaphore.acquire(blocking=True) |
| + Info('Starting %s' % next_thread) |
| + next_thread.start() |
| + |
| + # Wait on the rest of the threads to finish. |
| + for thread in threads: |
| + thread.join() |
| + |
| + return [thread.GetOutput() for thread in threads] |
| + |
| + |
| +def _PregenerateUpdates(parser, options): |
| + """Determines all deltas that will be generated and generates them. |
| + |
| + This method effectively pre-generates the dev server cache for all tests. |
| + |
| + Args: |
| + parser: parser from main. |
| + options: options from parsed parser. |
| + Returns: |
| + Array of output from generating updates. |
| + """ |
| + def _GenerateVMUpdate(target, src): |
| + """Generates an update using the devserver.""" |
| + RunCommand(['./start_devserver', |
| + '--pregenerate_update', |
| + '--exit', |
| + '--image=%s' % src, |
| + '--src_image=%s' % target, |
| + ], redirect_stderr=True) |
| + |
| + # Get the list of deltas by mocking out update method in test class. |
| + GenerateVirtualAUDeltasTest.ProcessOptions(parser, options) |
| + test_loader = unittest.TestLoader() |
| + test_loader.testMethodPrefix = options.test_prefix |
| + test_suite = test_loader.loadTestsFromTestCase(GenerateVirtualAUDeltasTest) |
| + test_result = unittest.TextTestRunner(verbosity=0).run(test_suite) |
| + |
| + Info('The following delta updates are required.') |
| + jobs = [] |
| + args = [] |
| + for target, srcs in GenerateVirtualAUDeltasTest.delta_list.items(): |
| + for src in srcs: |
| + print >> sys.stderr, '%s -> %s' % (src, target) |
| + jobs.append(_GenerateVMUpdate) |
| + args.append((target, src)) |
| + |
| + results = _RunParallelJobs(options.jobs, jobs, args) |
| + return results |
| + |
| + |
| def main(): |
| parser = optparse.OptionParser() |
| parser.add_option('-b', '--base_image', |
| @@ -619,10 +746,12 @@ def main(): |
| help='Disable using delta updates.') |
| parser.add_option('--no_graphics', action='store_true', |
| help='Disable graphics for the vm test.') |
| - parser.add_option('-m', '--remote', |
| - help='Remote address for real test.') |
| + parser.add_option('-j', '--jobs', default=8, type=int, |
| + help='Number of simultaneous jobs') |
| parser.add_option('-q', '--quick_test', default=False, action='store_true', |
| help='Use a basic test to verify image.') |
| + parser.add_option('-m', '--remote', |
| + help='Remote address for real test.') |
| parser.add_option('-t', '--target_image', |
| help='path to the target image.') |
| parser.add_option('--test_prefix', default='test', |
| @@ -642,15 +771,19 @@ def main(): |
| elif options.type == 'real': test_class = RealAUTest |
| else: parser.error('Could not parse harness type %s.' % options.type) |
| - test_class.ProcessOptions(parser, options) |
| + # TODO(sosa): Caching doesn't work with non-delta updates. Also, it doesn't |
| + # really make sense on non-vm images (yet). |
| + if options.delta and options.type == 'vm': |
| + _PregenerateUpdates(parser, options) |
| + # Run the test suite. |
| + test_class.ProcessOptions(parser, options) |
| test_loader = unittest.TestLoader() |
| test_loader.testMethodPrefix = options.test_prefix |
| test_suite = test_loader.loadTestsFromTestCase(test_class) |
| test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) |
| - |
| if not test_result.wasSuccessful(): |
| - Die('Test harness was not successful') |
| + Die('Test harness was not successful.') |
| if __name__ == '__main__': |