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

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: Last upload b4 commit 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 else:
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 # Needed for VM delta updates. We need to use the qemu image rather
509 # than the base image on a first update. By tracking the first_update
510 # we can set src_image to the qemu form of the base image when
511 # performing generating the delta payload.
512 self._first_update = True
511 self.vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname( 513 self.vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname(
512 image_path) 514 image_path)
513
514 Info('Creating: %s' % self.vm_image_path)
515
516 if not os.path.exists(self.vm_image_path): 515 if not os.path.exists(self.vm_image_path):
517 Info('Qemu image %s not found, creating one.' % self.vm_image_path) 516 Info('Creating %s' % vm_image_path)
518 RunCommand(['%s/image_to_vm.sh' % self.crosutils, 517 RunCommand(['%s/image_to_vm.sh' % self.crosutils,
519 '--full', 518 '--full',
520 '--from=%s' % ReinterpretPathForChroot( 519 '--from=%s' % ReinterpretPathForChroot(
521 os.path.dirname(image_path)), 520 os.path.dirname(image_path)),
522 '--vdisk_size=%s' % self._FULL_VDISK_SIZE, 521 '--vdisk_size=%s' % self._FULL_VDISK_SIZE,
523 '--statefulfs_size=%s' % self._FULL_STATEFULFS_SIZE, 522 '--statefulfs_size=%s' % self._FULL_STATEFULFS_SIZE,
524 '--board=%s' % self.board, 523 '--board=%s' % self.board,
525 '--test_image'], enter_chroot=True) 524 '--test_image'], enter_chroot=True)
526 else:
527 Info('Using existing VM image %s' % self.vm_image_path)
528 525
529 Info('Testing for %s' % self.vm_image_path) 526 Info('Using %s as base' % self.vm_image_path)
530 self.assertTrue(os.path.exists(self.vm_image_path)) 527 self.assertTrue(os.path.exists(self.vm_image_path))
531 528
532 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', 529 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old',
533 proxy_port=None): 530 proxy_port=None):
534 """Updates VM image with image_path.""" 531 """Updates VM image with image_path."""
535 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) 532 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change)
536 if src_image_path == self.base_image_path: 533 if src_image_path and self._first_update:
537 src_image_path = self.vm_image_path 534 src_image_path = self.vm_image_path
535 self._first_update = False
538 536
539 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, 537 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin,
540 '--update_image_path=%s' % image_path, 538 '--update_image_path=%s' % image_path,
541 '--vm_image_path=%s' % self.vm_image_path, 539 '--vm_image_path=%s' % self.vm_image_path,
542 '--snapshot', 540 '--snapshot',
543 self.graphics_flag, 541 self.graphics_flag,
544 '--persist', 542 '--persist',
545 '--kvm_pid=%s' % self._KVM_PID_FILE, 543 '--kvm_pid=%s' % self._KVM_PID_FILE,
546 stateful_change_flag, 544 stateful_change_flag,
547 '--src_image=%s' % src_image_path, 545 '--src_image=%s' % src_image_path,
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
601 ] 599 ]
602 600
603 if self.graphics_flag: 601 if self.graphics_flag:
604 commandWithArgs.append(self.graphics_flag) 602 commandWithArgs.append(self.graphics_flag)
605 603
606 output = RunCommand(commandWithArgs, error_ok=True, enter_chroot=False, 604 output = RunCommand(commandWithArgs, error_ok=True, enter_chroot=False,
607 redirect_stdout=True) 605 redirect_stdout=True)
608 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) 606 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass)
609 607
610 608
609 class GenerateVirtualAUDeltasTest(VirtualAUTest):
610 """Class the overrides VirtualAUTest and stores deltas we will generate."""
611 delta_list = {}
612
613 def setUp(self):
614 AUTest.setUp(self)
615
616 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old',
617 proxy_port=None):
618 if src_image_path and self._first_update:
619 src_image_path = self.vm_image_path
620 self._first_update = False
621
622 image_path = ReinterpretPathForChroot(image_path)
623 if src_image_path:
624 src_image_path = ReinterpretPathForChroot(src_image_path)
625 if not self.delta_list.has_key(image_path):
626 self.delta_list[image_path] = set([src_image_path])
627 else:
628 self.delta_list[image_path].add(src_image_path)
629
630 def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg):
631 pass
632
633 def VerifyImage(self, percent_required_to_pass):
634 pass
635
636
637 class ParallelJob(threading.Thread):
638 """Small wrapper for threading.Thread that releases a semaphore on exit."""
639 def __init__(self, semaphore, target, args):
640 threading.Thread.__init__(self, target=target, args=args)
641 self._target = target
642 self._args = args
643 self._semaphore = semaphore
644 self._output = None
645 self._completed = False
646
647 def run(self):
648 try:
649 threading.Thread.run(self)
650 finally:
651 self._Cleanup()
652 self._completed = True
653
654 def GetOutput(self):
655 assert self._completed, 'GetOutput called before thread was run.'
656 return self._output
657
658 def _Cleanup(self):
659 self._semaphore.release()
660
661 def __str__(self):
662 return '%s(%s)' % (self._target, self._args)
663
664
665 def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args):
666 """Runs set number of specified jobs in parallel.
667
668 Args:
669 number_of_simultaneous_jobs: Max number of threads to be run in parallel.
670 jobs: Array of methods to run.
671 jobs_args: Array of args associated with method calls.
672 Returns:
673 Returns an array of results corresponding to each thread.
674 """
675 def _TwoTupleize(x, y):
676 return (x, y)
677
678 threads = []
679 job_pool_semaphore = threading.Semaphore(number_of_sumultaneous_jobs)
680 assert len(jobs) == len(jobs_args), 'Length of args array is wrong.'
681
682 # Create the parallel jobs.
683 for job, args in map(_TwoTupleize, jobs, jobs_args):
684 thread = ParallelJob(job_pool_semaphore, target=job, args=args)
685 threads.append(thread)
686
687 # We use a semaphore to ensure we don't run more jobs that required.
688 # After each thread finishes, it releases (increments semaphore).
689 # Acquire blocks of num jobs reached and continues when a thread finishes.
690 for next_thread in threads:
691 job_pool_semaphore.acquire(blocking=True)
692 Info('Starting %s' % next_thread)
693 next_thread.start()
694
695 # Wait on the rest of the threads to finish.
696 for thread in threads:
697 thread.join()
698
699 return [thread.GetOutput() for thread in threads]
700
701
702 def _PregenerateUpdates(parser, options):
703 """Determines all deltas that will be generated and generates them.
704
705 This method effectively pre-generates the dev server cache for all tests.
706
707 Args:
708 parser: parser from main.
709 options: options from parsed parser.
710 Returns:
711 Array of output from generating updates.
712 """
713 def _GenerateVMUpdate(target, src):
714 """Generates an update using the devserver."""
715 RunCommand(['./start_devserver',
716 '--pregenerate_update',
717 '--exit',
718 '--image=%s' % target,
719 '--src_image=%s' % src,
720 '--for_vm'
721 ])
722
723 # Get the list of deltas by mocking out update method in test class.
724 GenerateVirtualAUDeltasTest.ProcessOptions(parser, options)
725 test_loader = unittest.TestLoader()
726 test_loader.testMethodPrefix = options.test_prefix
727 test_suite = test_loader.loadTestsFromTestCase(GenerateVirtualAUDeltasTest)
728 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite)
729
730 Info('The following delta updates are required.')
731 jobs = []
732 args = []
733 for target, srcs in GenerateVirtualAUDeltasTest.delta_list.items():
734 for src in srcs:
735 if src:
736 print >> sys.stderr, 'DELTA AU %s -> %s' % (src, target)
737 else:
738 print >> sys.stderr, 'FULL AU %s' % target
739
740 jobs.append(_GenerateVMUpdate)
741 args.append((target, src))
742
743 results = _RunParallelJobs(options.jobs, jobs, args)
744 return results
745
746
611 def main(): 747 def main():
612 parser = optparse.OptionParser() 748 parser = optparse.OptionParser()
613 parser.add_option('-b', '--base_image', 749 parser.add_option('-b', '--base_image',
614 help='path to the base image.') 750 help='path to the base image.')
615 parser.add_option('-r', '--board', 751 parser.add_option('-r', '--board',
616 help='board for the images.') 752 help='board for the images.')
617 parser.add_option('--no_delta', action='store_false', default=True, 753 parser.add_option('--no_delta', action='store_false', default=True,
618 dest='delta', 754 dest='delta',
619 help='Disable using delta updates.') 755 help='Disable using delta updates.')
620 parser.add_option('--no_graphics', action='store_true', 756 parser.add_option('--no_graphics', action='store_true',
621 help='Disable graphics for the vm test.') 757 help='Disable graphics for the vm test.')
758 parser.add_option('-j', '--jobs', default=8, type=int,
759 help='Number of simultaneous jobs')
760 parser.add_option('-q', '--quick_test', default=False, action='store_true',
761 help='Use a basic test to verify image.')
622 parser.add_option('-m', '--remote', 762 parser.add_option('-m', '--remote',
623 help='Remote address for real test.') 763 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', 764 parser.add_option('-t', '--target_image',
627 help='path to the target image.') 765 help='path to the target image.')
628 parser.add_option('--test_prefix', default='test', 766 parser.add_option('--test_prefix', default='test',
629 help='Only runs tests with specific prefix i.e. ' 767 help='Only runs tests with specific prefix i.e. '
630 'testFullUpdateWipeStateful.') 768 'testFullUpdateWipeStateful.')
631 parser.add_option('-p', '--type', default='vm', 769 parser.add_option('-p', '--type', default='vm',
632 help='type of test to run: [vm, real]. Default: vm.') 770 help='type of test to run: [vm, real]. Default: vm.')
633 parser.add_option('--verbose', default=True, action='store_true', 771 parser.add_option('--verbose', default=True, action='store_true',
634 help='Print out rather than capture output as much as ' 772 help='Print out rather than capture output as much as '
635 'possible.') 773 'possible.')
636 (options, leftover_args) = parser.parse_args() 774 (options, leftover_args) = parser.parse_args()
637 775
638 if leftover_args: 776 if leftover_args:
639 parser.error('Found extra options we do not support: %s' % leftover_args) 777 parser.error('Found extra options we do not support: %s' % leftover_args)
640 778
641 if options.type == 'vm': test_class = VirtualAUTest 779 if options.type == 'vm': test_class = VirtualAUTest
642 elif options.type == 'real': test_class = RealAUTest 780 elif options.type == 'real': test_class = RealAUTest
643 else: parser.error('Could not parse harness type %s.' % options.type) 781 else: parser.error('Could not parse harness type %s.' % options.type)
644 782
783 # TODO(sosa): Caching doesn't really make sense on non-vm images (yet).
784 if options.type == 'vm':
785 _PregenerateUpdates(parser, options)
786
787 # Run the test suite.
645 test_class.ProcessOptions(parser, options) 788 test_class.ProcessOptions(parser, options)
646
647 test_loader = unittest.TestLoader() 789 test_loader = unittest.TestLoader()
648 test_loader.testMethodPrefix = options.test_prefix 790 test_loader.testMethodPrefix = options.test_prefix
649 test_suite = test_loader.loadTestsFromTestCase(test_class) 791 test_suite = test_loader.loadTestsFromTestCase(test_class)
650 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) 792 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite)
651
652 if not test_result.wasSuccessful(): 793 if not test_result.wasSuccessful():
653 Die('Test harness was not successful') 794 Die('Test harness was not successful.')
654 795
655 796
656 if __name__ == '__main__': 797 if __name__ == '__main__':
657 main() 798 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