| 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 subprocess | |
| 19 import sys | 18 import sys |
| 20 import threading | 19 import threading |
| 21 import time | 20 import time |
| 22 import unittest | 21 import unittest |
| 23 import urllib | 22 import urllib |
| 24 | 23 |
| 25 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) | 24 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) |
| 26 from cros_build_lib import Die | 25 from cros_build_lib import Die |
| 27 from cros_build_lib import GetIPAddress | |
| 28 from cros_build_lib import Info | 26 from cros_build_lib import Info |
| 29 from cros_build_lib import ReinterpretPathForChroot | 27 from cros_build_lib import ReinterpretPathForChroot |
| 30 from cros_build_lib import RunCommand | 28 from cros_build_lib import RunCommand |
| 31 from cros_build_lib import RunCommandCaptureOutput | 29 from cros_build_lib import RunCommandCaptureOutput |
| 32 from cros_build_lib import Warning | 30 from cros_build_lib import Warning |
| 33 | 31 |
| 34 import cros_test_proxy | 32 import cros_test_proxy |
| 35 | 33 |
| 36 global dev_server_cache | |
| 37 | |
| 38 | 34 |
| 39 class UpdateException(Exception): | 35 class UpdateException(Exception): |
| 40 """Exception thrown when _UpdateImage or _UpdateUsingPayload fail""" | 36 """Exception thrown when _UpdateImage or _UpdateUsingPayload fail""" |
| 41 def __init__(self, code, stdout): | 37 def __init__(self, code, stdout): |
| 42 self.code = code | 38 self.code = code |
| 43 self.stdout = stdout | 39 self.stdout = stdout |
| 44 | 40 |
| 45 | 41 |
| 46 class AUTest(object): | 42 class AUTest(object): |
| 47 """Abstract interface that defines an Auto Update test.""" | 43 """Abstract interface that defines an Auto Update test.""" |
| (...skipping 418 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 466 output = RunCommand([ | 462 output = RunCommand([ |
| 467 '%s/run_remote_tests.sh' % self.crosutils, | 463 '%s/run_remote_tests.sh' % self.crosutils, |
| 468 '--remote=%s' % self.remote, | 464 '--remote=%s' % self.remote, |
| 469 self.verify_suite, | 465 self.verify_suite, |
| 470 ], error_ok=True, enter_chroot=False, redirect_stdout=True) | 466 ], error_ok=True, enter_chroot=False, redirect_stdout=True) |
| 471 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) | 467 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) |
| 472 | 468 |
| 473 | 469 |
| 474 class VirtualAUTest(unittest.TestCase, AUTest): | 470 class VirtualAUTest(unittest.TestCase, AUTest): |
| 475 """Test harness for updating virtual machines.""" | 471 """Test harness for updating virtual machines.""" |
| 472 vm_image_path = None |
| 476 | 473 |
| 477 # VM Constants. | 474 # VM Constants. |
| 478 _FULL_VDISK_SIZE = 6072 | 475 _FULL_VDISK_SIZE = 6072 |
| 479 _FULL_STATEFULFS_SIZE = 3074 | 476 _FULL_STATEFULFS_SIZE = 3074 |
| 480 | 477 _KVM_PID_FILE = '/tmp/harness_pid' |
| 481 # Class variables used to acquire individual VM variables per test. | |
| 482 _vm_lock = threading.Lock() | |
| 483 _next_port = 9222 | |
| 484 | 478 |
| 485 def _KillExistingVM(self, pid_file): | 479 def _KillExistingVM(self, pid_file): |
| 486 if os.path.exists(pid_file): | 480 if os.path.exists(pid_file): |
| 487 Warning('Existing %s found. Deleting and killing process' % | 481 Warning('Existing %s found. Deleting and killing process' % |
| 488 pid_file) | 482 pid_file) |
| 489 RunCommand(['./cros_stop_vm', '--kvm_pid=%s' % pid_file], | 483 RunCommand(['./cros_stop_vm', '--kvm_pid=%s' % pid_file], |
| 490 cwd=self.crosutilsbin) | 484 cwd=self.crosutilsbin) |
| 491 | 485 |
| 492 assert not os.path.exists(pid_file) | 486 assert not os.path.exists(pid_file) |
| 493 | 487 |
| 494 def _AcquireUniquePortAndPidFile(self): | |
| 495 """Acquires unique ssh port and pid file for VM.""" | |
| 496 with VirtualAUTest._vm_lock: | |
| 497 self._ssh_port = VirtualAUTest._next_port | |
| 498 self._kvm_pid_file = '/tmp/kvm.%d' % self._ssh_port | |
| 499 VirtualAUTest._next_port += 1 | |
| 500 | |
| 501 def setUp(self): | 488 def setUp(self): |
| 502 """Unit test overriden method. Is called before every test.""" | 489 """Unit test overriden method. Is called before every test.""" |
| 503 AUTest.setUp(self) | 490 AUTest.setUp(self) |
| 504 self.vm_image_path = None | 491 self._KillExistingVM(self._KVM_PID_FILE) |
| 505 self._AcquireUniquePortAndPidFile() | |
| 506 self._KillExistingVM(self._kvm_pid_file) | |
| 507 | |
| 508 def tearDown(self): | |
| 509 self._KillExistingVM(self._kvm_pid_file) | |
| 510 | 492 |
| 511 @classmethod | 493 @classmethod |
| 512 def ProcessOptions(cls, parser, options): | 494 def ProcessOptions(cls, parser, options): |
| 513 """Processes vm-specific options.""" | 495 """Processes vm-specific options.""" |
| 514 AUTest.ProcessOptions(parser, options) | 496 AUTest.ProcessOptions(parser, options) |
| 515 cls.board = options.board | 497 cls.board = options.board |
| 516 | 498 |
| 517 # Communicate flags to tests. | 499 # Communicate flags to tests. |
| 518 cls.graphics_flag = '' | 500 cls.graphics_flag = '' |
| 519 if options.no_graphics: cls.graphics_flag = '--no_graphics' | 501 if options.no_graphics: cls.graphics_flag = '--no_graphics' |
| (...skipping 18 matching lines...) Expand all Loading... |
| 538 os.path.dirname(image_path)), | 520 os.path.dirname(image_path)), |
| 539 '--vdisk_size=%s' % self._FULL_VDISK_SIZE, | 521 '--vdisk_size=%s' % self._FULL_VDISK_SIZE, |
| 540 '--statefulfs_size=%s' % self._FULL_STATEFULFS_SIZE, | 522 '--statefulfs_size=%s' % self._FULL_STATEFULFS_SIZE, |
| 541 '--board=%s' % self.board, | 523 '--board=%s' % self.board, |
| 542 '--test_image'], enter_chroot=True) | 524 '--test_image'], enter_chroot=True) |
| 543 | 525 |
| 544 Info('Using %s as base' % self.vm_image_path) | 526 Info('Using %s as base' % self.vm_image_path) |
| 545 self.assertTrue(os.path.exists(self.vm_image_path)) | 527 self.assertTrue(os.path.exists(self.vm_image_path)) |
| 546 | 528 |
| 547 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', | 529 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', |
| 548 proxy_port=''): | 530 proxy_port=None): |
| 549 """Updates VM image with image_path.""" | 531 """Updates VM image with image_path.""" |
| 550 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) | 532 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) |
| 551 if src_image_path and self._first_update: | 533 if src_image_path and self._first_update: |
| 552 src_image_path = self.vm_image_path | 534 src_image_path = self.vm_image_path |
| 553 self._first_update = False | 535 self._first_update = False |
| 554 | 536 |
| 555 # Check image payload cache first. | 537 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, |
| 556 update_id = _GenerateUpdateId(target=image_path, src=src_image_path) | 538 '--update_image_path=%s' % image_path, |
| 557 cache_path = dev_server_cache[update_id] | 539 '--vm_image_path=%s' % self.vm_image_path, |
| 558 if cache_path: | 540 '--snapshot', |
| 559 Info('Using cache %s' % cache_path) | 541 self.graphics_flag, |
| 560 update_url = DevServerWrapper.GetDevServerURL(proxy_port, cache_path) | 542 '--persist', |
| 561 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, | 543 '--kvm_pid=%s' % self._KVM_PID_FILE, |
| 562 '--vm_image_path=%s' % self.vm_image_path, | 544 stateful_change_flag, |
| 563 '--snapshot', | 545 '--src_image=%s' % src_image_path, |
| 564 self.graphics_flag, | 546 ] |
| 565 '--persist', | 547 |
| 566 '--kvm_pid=%s' % self._kvm_pid_file, | 548 if proxy_port: |
| 567 '--ssh_port=%s' % self._ssh_port, | 549 cmd.append('--proxy_port=%s' % proxy_port) |
| 568 stateful_change_flag, | |
| 569 '--update_url=%s' % update_url, | |
| 570 ] | |
| 571 else: | |
| 572 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, | |
| 573 '--update_image_path=%s' % image_path, | |
| 574 '--vm_image_path=%s' % self.vm_image_path, | |
| 575 '--snapshot', | |
| 576 self.graphics_flag, | |
| 577 '--persist', | |
| 578 '--kvm_pid=%s' % self._kvm_pid_file, | |
| 579 '--ssh_port=%s' % self._ssh_port, | |
| 580 stateful_change_flag, | |
| 581 '--src_image=%s' % src_image_path, | |
| 582 '--proxy_port=%s' % proxy_port | |
| 583 ] | |
| 584 | 550 |
| 585 if self.verbose: | 551 if self.verbose: |
| 586 try: | 552 try: |
| 587 RunCommand(cmd) | 553 RunCommand(cmd) |
| 588 except Exception, e: | 554 except Exception, e: |
| 589 raise UpdateException(1, e.message) | 555 raise UpdateException(1, e.message) |
| 590 else: | 556 else: |
| 591 (code, stdout, stderr) = RunCommandCaptureOutput(cmd) | 557 (code, stdout, stderr) = RunCommandCaptureOutput(cmd) |
| 592 if code != 0: | 558 if code != 0: |
| 593 raise UpdateException(code, stdout) | 559 raise UpdateException(code, stdout) |
| 594 | 560 |
| 595 def _UpdateUsingPayload(self, update_path, stateful_change='old', | 561 def _UpdateUsingPayload(self, update_path, stateful_change='old', |
| 596 proxy_port=None): | 562 proxy_port=None): |
| 597 """Updates a vm image using cros_run_vm_update.""" | 563 """Updates a vm image using cros_run_vm_update.""" |
| 598 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) | 564 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) |
| 599 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, | 565 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, |
| 600 '--payload=%s' % update_path, | 566 '--payload=%s' % update_path, |
| 601 '--vm_image_path=%s' % self.vm_image_path, | 567 '--vm_image_path=%s' % self.vm_image_path, |
| 602 '--snapshot', | 568 '--snapshot', |
| 603 self.graphics_flag, | 569 self.graphics_flag, |
| 604 '--persist', | 570 '--persist', |
| 605 '--kvm_pid=%s' % self._kvm_pid_file, | 571 '--kvm_pid=%s' % self._KVM_PID_FILE, |
| 606 '--ssh_port=%s' % self._ssh_port, | |
| 607 stateful_change_flag, | 572 stateful_change_flag, |
| 608 ] | 573 ] |
| 609 | 574 |
| 610 if proxy_port: | 575 if proxy_port: |
| 611 cmd.append('--proxy_port=%s' % proxy_port) | 576 cmd.append('--proxy_port=%s' % proxy_port) |
| 612 | 577 |
| 613 if self.verbose: | 578 if self.verbose: |
| 614 try: | 579 try: |
| 615 RunCommand(cmd) | 580 RunCommand(cmd) |
| 616 except Exception, e: | 581 except Exception, e: |
| 617 raise UpdateException(1, e.message) | 582 raise UpdateException(1, e.message) |
| 618 else: | 583 else: |
| 619 (code, stdout, stderr) = RunCommandCaptureOutput(cmd) | 584 (code, stdout, stderr) = RunCommandCaptureOutput(cmd) |
| 620 if code != 0: | 585 if code != 0: |
| 621 raise UpdateException(code, stdout) | 586 raise UpdateException(code, stdout) |
| 622 | 587 |
| 623 def VerifyImage(self, percent_required_to_pass): | 588 def VerifyImage(self, percent_required_to_pass): |
| 624 """Runs vm smoke suite to verify image.""" | 589 """Runs vm smoke suite to verify image.""" |
| 625 # image_to_live already verifies lsb-release matching. This is just | 590 # image_to_live already verifies lsb-release matching. This is just |
| 626 # for additional steps. | 591 # for additional steps. |
| 627 | 592 |
| 628 commandWithArgs = ['%s/cros_run_vm_test' % self.crosutilsbin, | 593 commandWithArgs = ['%s/cros_run_vm_test' % self.crosutilsbin, |
| 629 '--image_path=%s' % self.vm_image_path, | 594 '--image_path=%s' % self.vm_image_path, |
| 630 '--snapshot', | 595 '--snapshot', |
| 631 '--persist', | 596 '--persist', |
| 632 '--kvm_pid=%s' % self._kvm_pid_file, | 597 '--kvm_pid=%s' % self._KVM_PID_FILE, |
| 633 '--ssh_port=%s' % self._ssh_port, | |
| 634 self.verify_suite, | 598 self.verify_suite, |
| 635 ] | 599 ] |
| 636 | 600 |
| 637 if self.graphics_flag: | 601 if self.graphics_flag: |
| 638 commandWithArgs.append(self.graphics_flag) | 602 commandWithArgs.append(self.graphics_flag) |
| 639 | 603 |
| 640 output = RunCommand(commandWithArgs, error_ok=True, enter_chroot=False, | 604 output = RunCommand(commandWithArgs, error_ok=True, enter_chroot=False, |
| 641 redirect_stdout=True) | 605 redirect_stdout=True) |
| 642 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) | 606 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) |
| 643 | 607 |
| 644 | 608 |
| 645 class GenerateVirtualAUDeltasTest(VirtualAUTest): | 609 class GenerateVirtualAUDeltasTest(VirtualAUTest): |
| 646 """Class the overrides VirtualAUTest and stores deltas we will generate.""" | 610 """Class the overrides VirtualAUTest and stores deltas we will generate.""" |
| 647 delta_list = {} | 611 delta_list = {} |
| 648 | 612 |
| 649 def setUp(self): | 613 def setUp(self): |
| 650 AUTest.setUp(self) | 614 AUTest.setUp(self) |
| 651 | 615 |
| 652 def tearDown(self): | |
| 653 pass | |
| 654 | |
| 655 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', | 616 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', |
| 656 proxy_port=None): | 617 proxy_port=None): |
| 657 if src_image_path and self._first_update: | 618 if src_image_path and self._first_update: |
| 658 src_image_path = self.vm_image_path | 619 src_image_path = self.vm_image_path |
| 659 self._first_update = False | 620 self._first_update = False |
| 660 | 621 |
| 622 image_path = ReinterpretPathForChroot(image_path) |
| 623 if src_image_path: |
| 624 src_image_path = ReinterpretPathForChroot(src_image_path) |
| 661 if not self.delta_list.has_key(image_path): | 625 if not self.delta_list.has_key(image_path): |
| 662 self.delta_list[image_path] = set([src_image_path]) | 626 self.delta_list[image_path] = set([src_image_path]) |
| 663 else: | 627 else: |
| 664 self.delta_list[image_path].add(src_image_path) | 628 self.delta_list[image_path].add(src_image_path) |
| 665 | 629 |
| 666 def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg): | 630 def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg): |
| 667 pass | 631 pass |
| 668 | 632 |
| 669 def VerifyImage(self, percent_required_to_pass): | 633 def VerifyImage(self, percent_required_to_pass): |
| 670 pass | 634 pass |
| 671 | 635 |
| 672 | 636 |
| 673 class ParallelJob(threading.Thread): | 637 class ParallelJob(threading.Thread): |
| 674 """Small wrapper for threading. Thread that releases a semaphores on exit.""" | 638 """Small wrapper for threading.Thread that releases a semaphore on exit.""" |
| 675 | 639 def __init__(self, semaphore, target, args): |
| 676 def __init__(self, starting_semaphore, ending_semaphore, target, args): | |
| 677 """Initializes an instance of a job. | |
| 678 | |
| 679 Args: | |
| 680 starting_semaphore: Semaphore used by caller to wait on such that | |
| 681 there isn't more than a certain number of threads running. Should | |
| 682 be initialized to a value for the number of threads wanting to be run | |
| 683 at a time. | |
| 684 ending_semaphore: Semaphore is released every time a job ends. Should be | |
| 685 initialized to 0 before starting first job. Should be acquired once for | |
| 686 each job. Threading.Thread.join() has a bug where if the run function | |
| 687 terminates too quickly join() will hang forever. | |
| 688 target: The func to run. | |
| 689 args: Args to pass to the fun. | |
| 690 """ | |
| 691 threading.Thread.__init__(self, target=target, args=args) | 640 threading.Thread.__init__(self, target=target, args=args) |
| 692 self._target = target | 641 self._target = target |
| 693 self._args = args | 642 self._args = args |
| 694 self._starting_semaphore = starting_semaphore | 643 self._semaphore = semaphore |
| 695 self._ending_semaphore = ending_semaphore | |
| 696 self._output = None | 644 self._output = None |
| 697 self._completed = False | 645 self._completed = False |
| 698 | 646 |
| 699 def run(self): | 647 def run(self): |
| 700 """Thread override. Runs the method specified and sets output.""" | |
| 701 try: | 648 try: |
| 702 self._output = self._target(*self._args) | 649 threading.Thread.run(self) |
| 703 finally: | 650 finally: |
| 704 # From threading.py to avoid a refcycle. | |
| 705 del self._target, self._args | |
| 706 # Our own clean up. | |
| 707 self._Cleanup() | 651 self._Cleanup() |
| 708 self._completed = True | 652 self._completed = True |
| 709 | 653 |
| 710 def GetOutput(self): | 654 def GetOutput(self): |
| 711 """Returns the output of the method run.""" | |
| 712 assert self._completed, 'GetOutput called before thread was run.' | 655 assert self._completed, 'GetOutput called before thread was run.' |
| 713 return self._output | 656 return self._output |
| 714 | 657 |
| 715 def _Cleanup(self): | 658 def _Cleanup(self): |
| 716 """Releases semaphores for a waiting caller.""" | 659 self._semaphore.release() |
| 717 self._starting_semaphore.release() | |
| 718 self._ending_semaphore.release() | |
| 719 | 660 |
| 720 def __str__(self): | 661 def __str__(self): |
| 721 return '%s(%s)' % (self._target, self._args) | 662 return '%s(%s)' % (self._target, self._args) |
| 722 | 663 |
| 723 | 664 |
| 724 class DevServerWrapper(threading.Thread): | |
| 725 """A Simple wrapper around a devserver instance.""" | |
| 726 | |
| 727 def __init__(self): | |
| 728 self.proc = None | |
| 729 threading.Thread.__init__(self) | |
| 730 | |
| 731 def run(self): | |
| 732 # Kill previous running instance of devserver if it exists. | |
| 733 RunCommand(['sudo', 'pkill', '-f', 'devserver.py', ], error_ok=True, | |
| 734 print_cmd=False) | |
| 735 self.proc = subprocess.Popen(['sudo', | |
| 736 './start_devserver', | |
| 737 '--archive_dir=./static', | |
| 738 '--client_prefix=ChromeOSUpdateEngine', | |
| 739 '--production', | |
| 740 ]) | |
| 741 self.proc.communicate() | |
| 742 | |
| 743 def Stop(self): | |
| 744 """Kills the devserver instance.""" | |
| 745 self.proc.kill() | |
| 746 | |
| 747 @classmethod | |
| 748 def GetDevServerURL(cls, port, sub_dir): | |
| 749 """Returns the dev server url for a given port and sub directory.""" | |
| 750 ip_addr = GetIPAddress() | |
| 751 if not port: port = 8080 | |
| 752 url = 'http://%(ip)s:%(port)s/%(dir)s' % {'ip': ip_addr, | |
| 753 'port': str(port), | |
| 754 'dir': sub_dir} | |
| 755 return url | |
| 756 | |
| 757 | |
| 758 def _GenerateUpdateId(target, src): | |
| 759 """Returns a simple representation id of target and src paths.""" | |
| 760 if src: | |
| 761 return '%s->%s' % (target, src) | |
| 762 else: | |
| 763 return target | |
| 764 | |
| 765 | |
| 766 def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args): | 665 def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args): |
| 767 """Runs set number of specified jobs in parallel. | 666 """Runs set number of specified jobs in parallel. |
| 768 | 667 |
| 769 Args: | 668 Args: |
| 770 number_of_simultaneous_jobs: Max number of threads to be run in parallel. | 669 number_of_simultaneous_jobs: Max number of threads to be run in parallel. |
| 771 jobs: Array of methods to run. | 670 jobs: Array of methods to run. |
| 772 jobs_args: Array of args associated with method calls. | 671 jobs_args: Array of args associated with method calls. |
| 773 Returns: | 672 Returns: |
| 774 Returns an array of results corresponding to each thread. | 673 Returns an array of results corresponding to each thread. |
| 775 """ | 674 """ |
| 776 def _TwoTupleize(x, y): | 675 def _TwoTupleize(x, y): |
| 777 return (x, y) | 676 return (x, y) |
| 778 | 677 |
| 779 threads = [] | 678 threads = [] |
| 780 job_start_semaphore = threading.Semaphore(number_of_sumultaneous_jobs) | 679 job_pool_semaphore = threading.Semaphore(number_of_sumultaneous_jobs) |
| 781 join_semaphore = threading.Semaphore(0) | |
| 782 assert len(jobs) == len(jobs_args), 'Length of args array is wrong.' | 680 assert len(jobs) == len(jobs_args), 'Length of args array is wrong.' |
| 783 | 681 |
| 784 # Create the parallel jobs. | 682 # Create the parallel jobs. |
| 785 for job, args in map(_TwoTupleize, jobs, jobs_args): | 683 for job, args in map(_TwoTupleize, jobs, jobs_args): |
| 786 thread = ParallelJob(job_start_semaphore, join_semaphore, target=job, | 684 thread = ParallelJob(job_pool_semaphore, target=job, args=args) |
| 787 args=args) | |
| 788 threads.append(thread) | 685 threads.append(thread) |
| 789 | 686 |
| 790 # We use a semaphore to ensure we don't run more jobs that required. | 687 # We use a semaphore to ensure we don't run more jobs that required. |
| 791 # After each thread finishes, it releases (increments semaphore). | 688 # After each thread finishes, it releases (increments semaphore). |
| 792 # Acquire blocks of num jobs reached and continues when a thread finishes. | 689 # Acquire blocks of num jobs reached and continues when a thread finishes. |
| 793 for next_thread in threads: | 690 for next_thread in threads: |
| 794 job_start_semaphore.acquire(blocking=True) | 691 job_pool_semaphore.acquire(blocking=True) |
| 795 Info('Starting job %s' % next_thread) | 692 Info('Starting %s' % next_thread) |
| 796 next_thread.start() | 693 next_thread.start() |
| 797 | 694 |
| 798 # Wait on the rest of the threads to finish. | 695 # Wait on the rest of the threads to finish. |
| 799 for thread in threads: | 696 for thread in threads: |
| 800 join_semaphore.acquire(blocking=True) | 697 thread.join() |
| 801 | 698 |
| 802 return [thread.GetOutput() for thread in threads] | 699 return [thread.GetOutput() for thread in threads] |
| 803 | 700 |
| 804 | 701 |
| 805 def _PrepareTestSuite(parser, options, test_class): | |
| 806 """Returns a prepared test suite given by the options and test class.""" | |
| 807 test_class.ProcessOptions(parser, options) | |
| 808 test_loader = unittest.TestLoader() | |
| 809 test_loader.testMethodPrefix = options.test_prefix | |
| 810 return test_loader.loadTestsFromTestCase(test_class) | |
| 811 | |
| 812 | |
| 813 def _PregenerateUpdates(parser, options): | 702 def _PregenerateUpdates(parser, options): |
| 814 """Determines all deltas that will be generated and generates them. | 703 """Determines all deltas that will be generated and generates them. |
| 815 | 704 |
| 816 This method effectively pre-generates the dev server cache for all tests. | 705 This method effectively pre-generates the dev server cache for all tests. |
| 817 | 706 |
| 818 Args: | 707 Args: |
| 819 parser: parser from main. | 708 parser: parser from main. |
| 820 options: options from parsed parser. | 709 options: options from parsed parser. |
| 821 Returns: | 710 Returns: |
| 822 Dictionary of Update Identifiers->Relative cache locations. | 711 Array of output from generating updates. |
| 823 """ | 712 """ |
| 824 def _GenerateVMUpdate(target, src): | 713 def _GenerateVMUpdate(target, src): |
| 825 """Generates an update using the devserver.""" | 714 """Generates an update using the devserver.""" |
| 826 target = ReinterpretPathForChroot(target) | 715 RunCommand(['sudo', |
| 827 if src: | 716 './start_devserver', |
| 828 src = ReinterpretPathForChroot(src) | 717 '--pregenerate_update', |
| 829 | 718 '--exit', |
| 830 return RunCommand(['sudo', | 719 '--image=%s' % target, |
| 831 './start_devserver', | 720 '--src_image=%s' % src, |
| 832 '--pregenerate_update', | 721 '--for_vm' |
| 833 '--exit', | 722 ], enter_chroot=True) |
| 834 '--image=%s' % target, | |
| 835 '--src_image=%s' % src, | |
| 836 '--for_vm', | |
| 837 ], redirect_stdout=True, enter_chroot=True, | |
| 838 print_cmd=False) | |
| 839 | 723 |
| 840 # Get the list of deltas by mocking out update method in test class. | 724 # Get the list of deltas by mocking out update method in test class. |
| 841 test_suite = _PrepareTestSuite(parser, options, GenerateVirtualAUDeltasTest) | 725 GenerateVirtualAUDeltasTest.ProcessOptions(parser, options) |
| 726 test_loader = unittest.TestLoader() |
| 727 test_loader.testMethodPrefix = options.test_prefix |
| 728 test_suite = test_loader.loadTestsFromTestCase(GenerateVirtualAUDeltasTest) |
| 842 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite) | 729 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite) |
| 843 | 730 |
| 844 Info('The following delta updates are required.') | 731 Info('The following delta updates are required.') |
| 845 update_ids = [] | |
| 846 jobs = [] | 732 jobs = [] |
| 847 args = [] | 733 args = [] |
| 848 for target, srcs in GenerateVirtualAUDeltasTest.delta_list.items(): | 734 for target, srcs in GenerateVirtualAUDeltasTest.delta_list.items(): |
| 849 for src in srcs: | 735 for src in srcs: |
| 850 update_id = _GenerateUpdateId(target=target, src=src) | 736 if src: |
| 851 print >> sys.stderr, 'AU: %s' % update_id | 737 print >> sys.stderr, 'DELTA AU %s -> %s' % (src, target) |
| 852 update_ids.append(update_id) | 738 else: |
| 739 print >> sys.stderr, 'FULL AU %s' % target |
| 740 |
| 853 jobs.append(_GenerateVMUpdate) | 741 jobs.append(_GenerateVMUpdate) |
| 854 args.append((target, src)) | 742 args.append((target, src)) |
| 855 | 743 |
| 856 raw_results = _RunParallelJobs(options.jobs, jobs, args) | 744 results = _RunParallelJobs(options.jobs, jobs, args) |
| 857 results = [] | 745 return results |
| 858 | |
| 859 # Parse the output. | |
| 860 key_line_re = re.compile('^PREGENERATED_UPDATE=([\w/.]+)') | |
| 861 for result in raw_results: | |
| 862 for line in result.splitlines(): | |
| 863 match = key_line_re.search(line) | |
| 864 if match: | |
| 865 # Convert blah/blah/update.gz -> update/blah/blah. | |
| 866 path_to_update_gz = match.group(1).rstrip() | |
| 867 (path_to_update_dir, _, _) = path_to_update_gz.rpartition('/update.gz') | |
| 868 results.append('/'.join(['update', path_to_update_dir])) | |
| 869 break | |
| 870 | |
| 871 assert len(raw_results) == len(results), \ | |
| 872 'Insufficient number cache directories returned.' | |
| 873 | |
| 874 # Build the dictionary from our id's and returned cache paths. | |
| 875 cache_dictionary = {} | |
| 876 for index, id in enumerate(update_ids): | |
| 877 cache_dictionary[id] = results[index] | |
| 878 | |
| 879 return cache_dictionary | |
| 880 | |
| 881 | |
| 882 def _RunTestsInParallel(parser, options, test_class): | |
| 883 """Runs the tests given by the options and test_class in parallel.""" | |
| 884 threads = [] | |
| 885 args = [] | |
| 886 test_suite = _PrepareTestSuite(parser, options, test_class) | |
| 887 for test in test_suite: | |
| 888 test_name = test.id() | |
| 889 test_case = unittest.TestLoader().loadTestsFromName(test_name) | |
| 890 threads.append(unittest.TextTestRunner().run) | |
| 891 args.append(test_case) | |
| 892 | |
| 893 results = _RunParallelJobs(options.jobs, threads, args) | |
| 894 if not (test_result.wasSuccessful() for test_result in results): | |
| 895 Die('Test harness was not successful') | |
| 896 | 746 |
| 897 | 747 |
| 898 def main(): | 748 def main(): |
| 899 parser = optparse.OptionParser() | 749 parser = optparse.OptionParser() |
| 900 parser.add_option('-b', '--base_image', | 750 parser.add_option('-b', '--base_image', |
| 901 help='path to the base image.') | 751 help='path to the base image.') |
| 902 parser.add_option('-r', '--board', | 752 parser.add_option('-r', '--board', |
| 903 help='board for the images.') | 753 help='board for the images.') |
| 904 parser.add_option('--no_delta', action='store_false', default=True, | 754 parser.add_option('--no_delta', action='store_false', default=True, |
| 905 dest='delta', | 755 dest='delta', |
| (...skipping 14 matching lines...) Expand all Loading... |
| 920 parser.add_option('-p', '--type', default='vm', | 770 parser.add_option('-p', '--type', default='vm', |
| 921 help='type of test to run: [vm, real]. Default: vm.') | 771 help='type of test to run: [vm, real]. Default: vm.') |
| 922 parser.add_option('--verbose', default=True, action='store_true', | 772 parser.add_option('--verbose', default=True, action='store_true', |
| 923 help='Print out rather than capture output as much as ' | 773 help='Print out rather than capture output as much as ' |
| 924 'possible.') | 774 'possible.') |
| 925 (options, leftover_args) = parser.parse_args() | 775 (options, leftover_args) = parser.parse_args() |
| 926 | 776 |
| 927 if leftover_args: | 777 if leftover_args: |
| 928 parser.error('Found extra options we do not support: %s' % leftover_args) | 778 parser.error('Found extra options we do not support: %s' % leftover_args) |
| 929 | 779 |
| 930 # Figure out the test_class. | |
| 931 if options.type == 'vm': test_class = VirtualAUTest | 780 if options.type == 'vm': test_class = VirtualAUTest |
| 932 elif options.type == 'real': test_class = RealAUTest | 781 elif options.type == 'real': test_class = RealAUTest |
| 933 else: parser.error('Could not parse harness type %s.' % options.type) | 782 else: parser.error('Could not parse harness type %s.' % options.type) |
| 934 | 783 |
| 935 # TODO(sosa): Caching doesn't really make sense on non-vm images (yet). | 784 # TODO(sosa): Caching doesn't really make sense on non-vm images (yet). |
| 936 global dev_server_cache | 785 if options.type == 'vm': |
| 937 if options.type == 'vm' and options.jobs > 1: | 786 _PregenerateUpdates(parser, options) |
| 938 dev_server_cache = _PregenerateUpdates(parser, options) | |
| 939 my_server = DevServerWrapper() | |
| 940 my_server.start() | |
| 941 try: | |
| 942 _RunTestsInParallel(parser, options, test_class) | |
| 943 finally: | |
| 944 my_server.Stop() | |
| 945 | 787 |
| 946 else: | 788 # Run the test suite. |
| 947 dev_server_cache = None | 789 test_class.ProcessOptions(parser, options) |
| 948 test_suite = _PrepareTestSuite(parser, options, test_class) | 790 test_loader = unittest.TestLoader() |
| 949 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) | 791 test_loader.testMethodPrefix = options.test_prefix |
| 950 if not test_result.wasSuccessful(): | 792 test_suite = test_loader.loadTestsFromTestCase(test_class) |
| 951 Die('Test harness was not successful.') | 793 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) |
| 794 if not test_result.wasSuccessful(): |
| 795 Die('Test harness was not successful.') |
| 952 | 796 |
| 953 | 797 |
| 954 if __name__ == '__main__': | 798 if __name__ == '__main__': |
| 955 main() | 799 main() |
| OLD | NEW |