OLD | NEW |
---|---|
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 | 2 |
3 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. | 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 | 4 # Use of this source code is governed by a BSD-style license that can be |
5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
6 | 6 |
7 """This module runs a suite of Auto Update tests. | 7 """This module runs a suite of Auto Update tests. |
8 | 8 |
9 The tests can be run on either a virtual machine or actual device depending | 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. | 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 | 11 Verbose is useful for many of the tests if you want to see individual commands |
12 being run during the update process. | 12 being run during the update process. |
13 """ | 13 """ |
14 | 14 |
15 import optparse | 15 import optparse |
16 import os | 16 import os |
17 import re | 17 import re |
18 import sys | 18 import sys |
19 import thread | 19 import threading |
20 import time | 20 import time |
21 import unittest | 21 import unittest |
22 import urllib | 22 import urllib |
23 | 23 |
24 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) | 24 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) |
25 from cros_build_lib import Die | 25 from cros_build_lib import Die |
26 from cros_build_lib import Info | 26 from cros_build_lib import Info |
27 from cros_build_lib import ReinterpretPathForChroot | 27 from cros_build_lib import ReinterpretPathForChroot |
28 from cros_build_lib import RunCommand | 28 from cros_build_lib import RunCommand |
29 from cros_build_lib import RunCommandCaptureOutput | 29 from cros_build_lib import RunCommandCaptureOutput |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
127 raise | 127 raise |
128 | 128 |
129 def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg): | 129 def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg): |
130 """Attempt a payload update, expect it to fail with expected log""" | 130 """Attempt a payload update, expect it to fail with expected log""" |
131 try: | 131 try: |
132 self._UpdateUsingPayload(payload) | 132 self._UpdateUsingPayload(payload) |
133 except UpdateException as err: | 133 except UpdateException as err: |
134 # Will raise ValueError if expected is not found. | 134 # Will raise ValueError if expected is not found. |
135 if re.search(re.escape(expected_msg), err.stdout, re.MULTILINE): | 135 if re.search(re.escape(expected_msg), err.stdout, re.MULTILINE): |
136 return | 136 return |
137 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.
| |
138 Warning("Didn't find '%s' in:" % expected_msg) | |
139 Warning(err.stdout) | |
137 | 140 |
138 Warning("Didn't find '%s' in:" % expected_msg) | |
139 Warning(err.stdout) | |
140 self.fail('We managed to update when failure was expected') | 141 self.fail('We managed to update when failure was expected') |
141 | 142 |
142 def AttemptUpdateWithFilter(self, filter, proxy_port=8081): | 143 def AttemptUpdateWithFilter(self, filter, proxy_port=8081): |
143 """Update through a proxy, with a specified filter, and expect success.""" | 144 """Update through a proxy, with a specified filter, and expect success.""" |
144 | 145 |
145 self.PrepareBase(self.target_image_path) | 146 self.PrepareBase(self.target_image_path) |
146 | 147 |
147 # The devserver runs at port 8080 by default. We assume that here, and | 148 # The devserver runs at port 8080 by default. We assume that here, and |
148 # start our proxy at a different. We then tell our update tools to | 149 # start our proxy at a different. We then tell our update tools to |
149 # have the client connect to our proxy_port instead of 8080. | 150 # have the client connect to our proxy_port instead of 8080. |
150 proxy = cros_test_proxy.CrosTestProxy(port_in=proxy_port, | 151 proxy = cros_test_proxy.CrosTestProxy(port_in=proxy_port, |
151 address_out='127.0.0.1', | 152 address_out='127.0.0.1', |
152 port_out=8080, | 153 port_out=8080, |
153 filter=filter) | 154 filter=filter) |
154 proxy.serve_forever_in_thread() | 155 proxy.serve_forever_in_thread() |
155 | 156 |
156 # This update is expected to fail... | 157 # This update is expected to fail... |
157 try: | 158 try: |
158 self.PerformUpdate(self.target_image_path, self.base_image_path, | 159 self.PerformUpdate(self.target_image_path, self.target_image_path, |
159 proxy_port=proxy_port) | 160 proxy_port=proxy_port) |
160 finally: | 161 finally: |
161 proxy.shutdown() | 162 proxy.shutdown() |
162 | 163 |
163 # -------- Functions that subclasses should override --------- | 164 # -------- Functions that subclasses should override --------- |
164 | 165 |
165 @classmethod | 166 @classmethod |
166 def ProcessOptions(cls, parser, options): | 167 def ProcessOptions(cls, parser, options): |
167 """Processes options. | 168 """Processes options. |
168 | 169 |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
241 rather than wiping it. | 242 rather than wiping it. |
242 """ | 243 """ |
243 # Just make sure some tests pass on original image. Some old images | 244 # Just make sure some tests pass on original image. Some old images |
244 # don't pass many tests. | 245 # don't pass many tests. |
245 self.PrepareBase(self.base_image_path) | 246 self.PrepareBase(self.base_image_path) |
246 # TODO(sosa): move to 100% once we start testing using the autotest paired | 247 # TODO(sosa): move to 100% once we start testing using the autotest paired |
247 # with the dev channel. | 248 # with the dev channel. |
248 percent_passed = self.VerifyImage(10) | 249 percent_passed = self.VerifyImage(10) |
249 | 250 |
250 # Update to - all tests should pass on new image. | 251 # Update to - all tests should pass on new image. |
251 Info('Updating from base image on vm to target image.') | |
252 self.PerformUpdate(self.target_image_path, self.base_image_path) | 252 self.PerformUpdate(self.target_image_path, self.base_image_path) |
253 self.VerifyImage(100) | 253 self.VerifyImage(100) |
254 | 254 |
255 # Update from - same percentage should pass that originally passed. | 255 # Update from - same percentage should pass that originally passed. |
256 Info('Updating from updated image on vm back to base image.') | |
257 self.PerformUpdate(self.base_image_path, self.target_image_path) | 256 self.PerformUpdate(self.base_image_path, self.target_image_path) |
258 self.VerifyImage(percent_passed) | 257 self.VerifyImage(percent_passed) |
259 | 258 |
260 def testUpdateWipeStateful(self): | 259 def testUpdateWipeStateful(self): |
261 """Tests if we can update after cleaning the stateful partition. | 260 """Tests if we can update after cleaning the stateful partition. |
262 | 261 |
263 This test checks that we can update successfully after wiping the | 262 This test checks that we can update successfully after wiping the |
264 stateful partition. | 263 stateful partition. |
265 """ | 264 """ |
266 # Just make sure some tests pass on original image. Some old images | 265 # Just make sure some tests pass on original image. Some old images |
267 # don't pass many tests. | 266 # don't pass many tests. |
268 self.PrepareBase(self.base_image_path) | 267 self.PrepareBase(self.base_image_path) |
269 # TODO(sosa): move to 100% once we start testing using the autotest paired | 268 # TODO(sosa): move to 100% once we start testing using the autotest paired |
270 # with the dev channel. | 269 # with the dev channel. |
271 percent_passed = self.VerifyImage(10) | 270 percent_passed = self.VerifyImage(10) |
272 | 271 |
273 # Update to - all tests should pass on new image. | 272 # Update to - all tests should pass on new image. |
274 Info('Updating from base image on vm to target image and wiping stateful.') | |
275 self.PerformUpdate(self.target_image_path, self.base_image_path, 'clean') | 273 self.PerformUpdate(self.target_image_path, self.base_image_path, 'clean') |
276 self.VerifyImage(100) | 274 self.VerifyImage(100) |
277 | 275 |
278 # Update from - same percentage should pass that originally passed. | 276 # Update from - same percentage should pass that originally passed. |
279 Info('Updating from updated image back to base image and wiping stateful.') | |
280 self.PerformUpdate(self.base_image_path, self.target_image_path, 'clean') | 277 self.PerformUpdate(self.base_image_path, self.target_image_path, 'clean') |
281 self.VerifyImage(percent_passed) | 278 self.VerifyImage(percent_passed) |
282 | 279 |
283 # TODO(sosa): Get test to work with verbose. | 280 # TODO(sosa): Get test to work with verbose. |
284 def NotestPartialUpdate(self): | 281 def NotestPartialUpdate(self): |
285 """Tests what happens if we attempt to update with a truncated payload.""" | 282 """Tests what happens if we attempt to update with a truncated payload.""" |
286 # Preload with the version we are trying to test. | 283 # Preload with the version we are trying to test. |
287 self.PrepareBase(self.target_image_path) | 284 self.PrepareBase(self.target_image_path) |
288 | 285 |
289 # Image can be updated at: | 286 # Image can be updated at: |
(...skipping 211 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
501 | 498 |
502 # Communicate flags to tests. | 499 # Communicate flags to tests. |
503 cls.graphics_flag = '' | 500 cls.graphics_flag = '' |
504 if options.no_graphics: cls.graphics_flag = '--no_graphics' | 501 if options.no_graphics: cls.graphics_flag = '--no_graphics' |
505 | 502 |
506 if not cls.board: | 503 if not cls.board: |
507 parser.error('Need board to convert base image to vm.') | 504 parser.error('Need board to convert base image to vm.') |
508 | 505 |
509 def PrepareBase(self, image_path): | 506 def PrepareBase(self, image_path): |
510 """Creates an update-able VM based on base image.""" | 507 """Creates an update-able VM based on base image.""" |
508 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.
| |
511 self.vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname( | 509 self.vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname( |
512 image_path) | 510 image_path) |
511 if not os.path.exists(self.vm_image_path): | |
512 Info('Creating %s' % vm_image_path) | |
513 RunCommand(['%s/image_to_vm.sh' % self.crosutils, | |
514 '--full', | |
515 '--from=%s' % ReinterpretPathForChroot( | |
516 os.path.dirname(image_path)), | |
517 '--vdisk_size=%s' % self._FULL_VDISK_SIZE, | |
518 '--statefulfs_size=%s' % self._FULL_STATEFULFS_SIZE, | |
519 '--board=%s' % self.board, | |
520 '--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.
| |
513 | 521 |
514 Info('Creating: %s' % self.vm_image_path) | 522 Info('Using %s as base' % self.vm_image_path) |
515 | |
516 if not os.path.exists(self.vm_image_path): | |
517 Info('Qemu image %s not found, creating one.' % self.vm_image_path) | |
518 RunCommand(['%s/image_to_vm.sh' % self.crosutils, | |
519 '--full', | |
520 '--from=%s' % ReinterpretPathForChroot( | |
521 os.path.dirname(image_path)), | |
522 '--vdisk_size=%s' % self._FULL_VDISK_SIZE, | |
523 '--statefulfs_size=%s' % self._FULL_STATEFULFS_SIZE, | |
524 '--board=%s' % self.board, | |
525 '--test_image'], enter_chroot=True) | |
526 else: | |
527 Info('Using existing VM image %s' % self.vm_image_path) | |
528 | |
529 Info('Testing for %s' % self.vm_image_path) | |
530 self.assertTrue(os.path.exists(self.vm_image_path)) | 523 self.assertTrue(os.path.exists(self.vm_image_path)) |
531 | 524 |
532 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', | 525 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', |
533 proxy_port=None): | 526 proxy_port=None): |
534 """Updates VM image with image_path.""" | 527 """Updates VM image with image_path.""" |
535 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) | 528 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) |
536 if src_image_path == self.base_image_path: | 529 if self._first_update: |
537 src_image_path = self.vm_image_path | 530 src_image_path = self.vm_image_path |
531 self._first_update = False | |
538 | 532 |
539 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, | 533 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, |
540 '--update_image_path=%s' % image_path, | 534 '--update_image_path=%s' % image_path, |
541 '--vm_image_path=%s' % self.vm_image_path, | 535 '--vm_image_path=%s' % self.vm_image_path, |
542 '--snapshot', | 536 '--snapshot', |
543 self.graphics_flag, | 537 self.graphics_flag, |
544 '--persist', | 538 '--persist', |
545 '--kvm_pid=%s' % self._KVM_PID_FILE, | 539 '--kvm_pid=%s' % self._KVM_PID_FILE, |
546 stateful_change_flag, | 540 stateful_change_flag, |
547 '--src_image=%s' % src_image_path, | 541 '--src_image=%s' % src_image_path, |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
601 ] | 595 ] |
602 | 596 |
603 if self.graphics_flag: | 597 if self.graphics_flag: |
604 commandWithArgs.append(self.graphics_flag) | 598 commandWithArgs.append(self.graphics_flag) |
605 | 599 |
606 output = RunCommand(commandWithArgs, error_ok=True, enter_chroot=False, | 600 output = RunCommand(commandWithArgs, error_ok=True, enter_chroot=False, |
607 redirect_stdout=True) | 601 redirect_stdout=True) |
608 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) | 602 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) |
609 | 603 |
610 | 604 |
605 class GenerateVirtualAUDeltasTest(VirtualAUTest): | |
606 """Class the overrides VirtualAUTest and stores deltas we will generate.""" | |
607 delta_list = {} | |
608 | |
609 def setUp(self): | |
610 AUTest.setUp(self) | |
611 | |
612 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', | |
613 proxy_port=None): | |
614 if self._first_update: | |
615 src_image_path = self.vm_image_path | |
616 self._first_update = False | |
617 | |
618 image_path = ReinterpretPathForChroot(image_path) | |
619 if src_image_path: | |
620 src_image_path = ReinterpretPathForChroot(src_image_path) | |
621 if not self.delta_list.has_key(image_path): | |
622 self.delta_list[image_path] = set([src_image_path]) | |
623 else: | |
624 self.delta_list[image_path].add(src_image_path) | |
625 | |
626 def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg): | |
627 pass | |
628 | |
629 def VerifyImage(self, percent_required_to_pass): | |
630 pass | |
631 | |
632 | |
633 class ParallelJob(threading.Thread): | |
634 """Small wrapper for threading.Thread that releases a semaphore on exit.""" | |
635 def __init__(self, semaphore, target, args): | |
636 threading.Thread.__init__(self, target=target, args=args) | |
637 self._target = target | |
638 self._args = args | |
639 self._semaphore = semaphore | |
640 self._output = None | |
641 self._completed = False | |
642 | |
643 def run(self): | |
644 try: | |
645 threading.Thread.run(self) | |
646 finally: | |
647 self._Cleanup() | |
648 self._completed = True | |
649 | |
650 def GetOutput(self): | |
651 assert self._completed, 'GetOutput called before thread was run.' | |
652 return self._output | |
653 | |
654 def _Cleanup(self): | |
655 self._semaphore.release() | |
656 | |
657 def __str__(self): | |
658 return '%s(%s)' % (self._target, self._args) | |
659 | |
660 | |
661 def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args): | |
662 """Runs set number of specified jobs in parallel. | |
663 | |
664 Args: | |
665 number_of_simultaneous_jobs: Max number of threads to be run in parallel. | |
666 jobs: Array of methods to run. | |
667 jobs_args: Array of args associated with method calls. | |
668 Returns: | |
669 Returns an array of results corresponding to each thread. | |
670 """ | |
671 def _TwoTupleize(x, y): | |
672 return (x, y) | |
673 | |
674 threads = [] | |
675 job_pool_semaphore = threading.Semaphore(number_of_sumultaneous_jobs) | |
676 assert len(jobs) == len(jobs_args), 'Length of args array is wrong.' | |
677 | |
678 # Create the parallel jobs. | |
679 for job, args in map(_TwoTupleize, jobs, jobs_args): | |
680 thread = ParallelJob(job_pool_semaphore, target=job, args=args) | |
681 threads.append(thread) | |
682 | |
683 # We use a semaphore to ensure we don't run more jobs that required. | |
684 # After each thread finishes, it releases (increments semaphore). | |
685 # Acquire blocks of num jobs reached and continues when a thread finishes. | |
686 for next_thread in threads: | |
687 job_pool_semaphore.acquire(blocking=True) | |
688 Info('Starting %s' % next_thread) | |
689 next_thread.start() | |
690 | |
691 # Wait on the rest of the threads to finish. | |
692 for thread in threads: | |
693 thread.join() | |
694 | |
695 return [thread.GetOutput() for thread in threads] | |
696 | |
697 | |
698 def _PregenerateUpdates(parser, options): | |
699 """Determines all deltas that will be generated and generates them. | |
700 | |
701 This method effectively pre-generates the dev server cache for all tests. | |
702 | |
703 Args: | |
704 parser: parser from main. | |
705 options: options from parsed parser. | |
706 Returns: | |
707 Array of output from generating updates. | |
708 """ | |
709 def _GenerateVMUpdate(target, src): | |
710 """Generates an update using the devserver.""" | |
711 RunCommand(['./start_devserver', | |
712 '--pregenerate_update', | |
713 '--exit', | |
714 '--image=%s' % src, | |
715 '--src_image=%s' % target, | |
716 ], redirect_stderr=True) | |
717 | |
718 # Get the list of deltas by mocking out update method in test class. | |
719 GenerateVirtualAUDeltasTest.ProcessOptions(parser, options) | |
720 test_loader = unittest.TestLoader() | |
721 test_loader.testMethodPrefix = options.test_prefix | |
722 test_suite = test_loader.loadTestsFromTestCase(GenerateVirtualAUDeltasTest) | |
723 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite) | |
724 | |
725 Info('The following delta updates are required.') | |
726 jobs = [] | |
727 args = [] | |
728 for target, srcs in GenerateVirtualAUDeltasTest.delta_list.items(): | |
729 for src in srcs: | |
730 print >> sys.stderr, '%s -> %s' % (src, target) | |
731 jobs.append(_GenerateVMUpdate) | |
732 args.append((target, src)) | |
733 | |
734 results = _RunParallelJobs(options.jobs, jobs, args) | |
735 return results | |
736 | |
737 | |
611 def main(): | 738 def main(): |
612 parser = optparse.OptionParser() | 739 parser = optparse.OptionParser() |
613 parser.add_option('-b', '--base_image', | 740 parser.add_option('-b', '--base_image', |
614 help='path to the base image.') | 741 help='path to the base image.') |
615 parser.add_option('-r', '--board', | 742 parser.add_option('-r', '--board', |
616 help='board for the images.') | 743 help='board for the images.') |
617 parser.add_option('--no_delta', action='store_false', default=True, | 744 parser.add_option('--no_delta', action='store_false', default=True, |
618 dest='delta', | 745 dest='delta', |
619 help='Disable using delta updates.') | 746 help='Disable using delta updates.') |
620 parser.add_option('--no_graphics', action='store_true', | 747 parser.add_option('--no_graphics', action='store_true', |
621 help='Disable graphics for the vm test.') | 748 help='Disable graphics for the vm test.') |
749 parser.add_option('-j', '--jobs', default=8, type=int, | |
750 help='Number of simultaneous jobs') | |
751 parser.add_option('-q', '--quick_test', default=False, action='store_true', | |
752 help='Use a basic test to verify image.') | |
622 parser.add_option('-m', '--remote', | 753 parser.add_option('-m', '--remote', |
623 help='Remote address for real test.') | 754 help='Remote address for real test.') |
624 parser.add_option('-q', '--quick_test', default=False, action='store_true', | |
625 help='Use a basic test to verify image.') | |
626 parser.add_option('-t', '--target_image', | 755 parser.add_option('-t', '--target_image', |
627 help='path to the target image.') | 756 help='path to the target image.') |
628 parser.add_option('--test_prefix', default='test', | 757 parser.add_option('--test_prefix', default='test', |
629 help='Only runs tests with specific prefix i.e. ' | 758 help='Only runs tests with specific prefix i.e. ' |
630 'testFullUpdateWipeStateful.') | 759 'testFullUpdateWipeStateful.') |
631 parser.add_option('-p', '--type', default='vm', | 760 parser.add_option('-p', '--type', default='vm', |
632 help='type of test to run: [vm, real]. Default: vm.') | 761 help='type of test to run: [vm, real]. Default: vm.') |
633 parser.add_option('--verbose', default=True, action='store_true', | 762 parser.add_option('--verbose', default=True, action='store_true', |
634 help='Print out rather than capture output as much as ' | 763 help='Print out rather than capture output as much as ' |
635 'possible.') | 764 'possible.') |
636 (options, leftover_args) = parser.parse_args() | 765 (options, leftover_args) = parser.parse_args() |
637 | 766 |
638 if leftover_args: | 767 if leftover_args: |
639 parser.error('Found extra options we do not support: %s' % leftover_args) | 768 parser.error('Found extra options we do not support: %s' % leftover_args) |
640 | 769 |
641 if options.type == 'vm': test_class = VirtualAUTest | 770 if options.type == 'vm': test_class = VirtualAUTest |
642 elif options.type == 'real': test_class = RealAUTest | 771 elif options.type == 'real': test_class = RealAUTest |
643 else: parser.error('Could not parse harness type %s.' % options.type) | 772 else: parser.error('Could not parse harness type %s.' % options.type) |
644 | 773 |
774 # TODO(sosa): Caching doesn't work with non-delta updates. Also, it doesn't | |
775 # really make sense on non-vm images (yet). | |
776 if options.delta and options.type == 'vm': | |
777 _PregenerateUpdates(parser, options) | |
778 | |
779 # Run the test suite. | |
645 test_class.ProcessOptions(parser, options) | 780 test_class.ProcessOptions(parser, options) |
646 | |
647 test_loader = unittest.TestLoader() | 781 test_loader = unittest.TestLoader() |
648 test_loader.testMethodPrefix = options.test_prefix | 782 test_loader.testMethodPrefix = options.test_prefix |
649 test_suite = test_loader.loadTestsFromTestCase(test_class) | 783 test_suite = test_loader.loadTestsFromTestCase(test_class) |
650 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) | 784 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) |
651 | |
652 if not test_result.wasSuccessful(): | 785 if not test_result.wasSuccessful(): |
653 Die('Test harness was not successful') | 786 Die('Test harness was not successful.') |
654 | 787 |
655 | 788 |
656 if __name__ == '__main__': | 789 if __name__ == '__main__': |
657 main() | 790 main() |
OLD | NEW |