Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(136)

Side by Side Diff: bin/cros_au_test_harness.py

Issue 6264005: Pre-generate updates for test harness in parallel. (Closed) Base URL: http://git.chromium.org/git/crosutils.git@master
Patch Set: Remove unused fun Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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()
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698