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. |
(...skipping 683 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
694 self._starting_semaphore = starting_semaphore | 694 self._starting_semaphore = starting_semaphore |
695 self._ending_semaphore = ending_semaphore | 695 self._ending_semaphore = ending_semaphore |
696 self._output = None | 696 self._output = None |
697 self._completed = False | 697 self._completed = False |
698 | 698 |
699 def run(self): | 699 def run(self): |
700 """Thread override. Runs the method specified and sets output.""" | 700 """Thread override. Runs the method specified and sets output.""" |
701 try: | 701 try: |
702 self._output = self._target(*self._args) | 702 self._output = self._target(*self._args) |
703 finally: | 703 finally: |
704 # From threading.py to avoid a refcycle. | |
705 del self._target, self._args | |
706 # Our own clean up. | 704 # Our own clean up. |
707 self._Cleanup() | 705 self._Cleanup() |
708 self._completed = True | 706 self._completed = True |
| 707 # From threading.py to avoid a refcycle. |
| 708 del self._target, self._args |
709 | 709 |
710 def GetOutput(self): | 710 def GetOutput(self): |
711 """Returns the output of the method run.""" | 711 """Returns the output of the method run.""" |
712 assert self._completed, 'GetOutput called before thread was run.' | 712 assert self._completed, 'GetOutput called before thread was run.' |
713 return self._output | 713 return self._output |
714 | 714 |
715 def _Cleanup(self): | 715 def _Cleanup(self): |
716 """Releases semaphores for a waiting caller.""" | 716 """Releases semaphores for a waiting caller.""" |
| 717 Info('Completed job %s' % self) |
717 self._starting_semaphore.release() | 718 self._starting_semaphore.release() |
718 self._ending_semaphore.release() | 719 self._ending_semaphore.release() |
719 | 720 |
720 def __str__(self): | 721 def __str__(self): |
721 return '%s(%s)' % (self._target, self._args) | 722 return '%s(%s)' % (self._target, self._args) |
722 | 723 |
723 | 724 |
724 class DevServerWrapper(threading.Thread): | 725 class DevServerWrapper(threading.Thread): |
725 """A Simple wrapper around a devserver instance.""" | 726 """A Simple wrapper around a devserver instance.""" |
726 | 727 |
(...skipping 29 matching lines...) Expand all Loading... |
756 | 757 |
757 | 758 |
758 def _GenerateUpdateId(target, src): | 759 def _GenerateUpdateId(target, src): |
759 """Returns a simple representation id of target and src paths.""" | 760 """Returns a simple representation id of target and src paths.""" |
760 if src: | 761 if src: |
761 return '%s->%s' % (target, src) | 762 return '%s->%s' % (target, src) |
762 else: | 763 else: |
763 return target | 764 return target |
764 | 765 |
765 | 766 |
766 def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args): | 767 def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args, print_status)
: |
| 768 |
767 """Runs set number of specified jobs in parallel. | 769 """Runs set number of specified jobs in parallel. |
768 | 770 |
769 Args: | 771 Args: |
770 number_of_simultaneous_jobs: Max number of threads to be run in parallel. | 772 number_of_simultaneous_jobs: Max number of threads to be run in parallel. |
771 jobs: Array of methods to run. | 773 jobs: Array of methods to run. |
772 jobs_args: Array of args associated with method calls. | 774 jobs_args: Array of args associated with method calls. |
| 775 print_status: True if you'd like this to print out .'s as it runs jobs. |
773 Returns: | 776 Returns: |
774 Returns an array of results corresponding to each thread. | 777 Returns an array of results corresponding to each thread. |
775 """ | 778 """ |
776 def _TwoTupleize(x, y): | 779 def _TwoTupleize(x, y): |
777 return (x, y) | 780 return (x, y) |
778 | 781 |
779 threads = [] | 782 threads = [] |
780 job_start_semaphore = threading.Semaphore(number_of_sumultaneous_jobs) | 783 job_start_semaphore = threading.Semaphore(number_of_sumultaneous_jobs) |
781 join_semaphore = threading.Semaphore(0) | 784 join_semaphore = threading.Semaphore(0) |
782 assert len(jobs) == len(jobs_args), 'Length of args array is wrong.' | 785 assert len(jobs) == len(jobs_args), 'Length of args array is wrong.' |
783 | 786 |
784 # Create the parallel jobs. | 787 # Create the parallel jobs. |
785 for job, args in map(_TwoTupleize, jobs, jobs_args): | 788 for job, args in map(_TwoTupleize, jobs, jobs_args): |
786 thread = ParallelJob(job_start_semaphore, join_semaphore, target=job, | 789 thread = ParallelJob(job_start_semaphore, join_semaphore, target=job, |
787 args=args) | 790 args=args) |
788 threads.append(thread) | 791 threads.append(thread) |
789 | 792 |
| 793 # Cache sudo access. |
| 794 RunCommand(['sudo', 'echo', 'Starting test harness'], |
| 795 print_cmd=False, redirect_stdout=True, redirect_stderr=True) |
| 796 |
790 # We use a semaphore to ensure we don't run more jobs that required. | 797 # We use a semaphore to ensure we don't run more jobs that required. |
791 # After each thread finishes, it releases (increments semaphore). | 798 # After each thread finishes, it releases (increments semaphore). |
792 # Acquire blocks of num jobs reached and continues when a thread finishes. | 799 # Acquire blocks of num jobs reached and continues when a thread finishes. |
793 for next_thread in threads: | 800 for next_thread in threads: |
794 job_start_semaphore.acquire(blocking=True) | 801 job_start_semaphore.acquire(blocking=True) |
795 Info('Starting job %s' % next_thread) | 802 Info('Starting job %s' % next_thread) |
796 next_thread.start() | 803 next_thread.start() |
797 | 804 |
798 # Wait on the rest of the threads to finish. | 805 # Wait on the rest of the threads to finish. |
| 806 Info('Waiting for threads to complete.') |
799 for thread in threads: | 807 for thread in threads: |
800 join_semaphore.acquire(blocking=True) | 808 while not join_semaphore.acquire(blocking=False): |
| 809 time.sleep(5) |
| 810 if print_status: |
| 811 print >> sys.stderr, '.', |
801 | 812 |
802 return [thread.GetOutput() for thread in threads] | 813 return [thread.GetOutput() for thread in threads] |
803 | 814 |
804 | 815 |
805 def _PrepareTestSuite(parser, options, test_class): | 816 def _PrepareTestSuite(parser, options, test_class): |
806 """Returns a prepared test suite given by the options and test class.""" | 817 """Returns a prepared test suite given by the options and test class.""" |
807 test_class.ProcessOptions(parser, options) | 818 test_class.ProcessOptions(parser, options) |
808 test_loader = unittest.TestLoader() | 819 test_loader = unittest.TestLoader() |
809 test_loader.testMethodPrefix = options.test_prefix | 820 test_loader.testMethodPrefix = options.test_prefix |
810 return test_loader.loadTestsFromTestCase(test_class) | 821 return test_loader.loadTestsFromTestCase(test_class) |
811 | 822 |
812 | 823 |
813 def _PregenerateUpdates(parser, options): | 824 def _PregenerateUpdates(parser, options): |
814 """Determines all deltas that will be generated and generates them. | 825 """Determines all deltas that will be generated and generates them. |
815 | 826 |
816 This method effectively pre-generates the dev server cache for all tests. | 827 This method effectively pre-generates the dev server cache for all tests. |
817 | 828 |
818 Args: | 829 Args: |
819 parser: parser from main. | 830 parser: parser from main. |
820 options: options from parsed parser. | 831 options: options from parsed parser. |
821 Returns: | 832 Returns: |
822 Dictionary of Update Identifiers->Relative cache locations. | 833 Dictionary of Update Identifiers->Relative cache locations. |
| 834 Raises: |
| 835 UpdateException if we fail to generate an update. |
823 """ | 836 """ |
824 def _GenerateVMUpdate(target, src): | 837 def _GenerateVMUpdate(target, src): |
825 """Generates an update using the devserver.""" | 838 """Generates an update using the devserver.""" |
826 target = ReinterpretPathForChroot(target) | 839 target = ReinterpretPathForChroot(target) |
827 if src: | 840 if src: |
828 src = ReinterpretPathForChroot(src) | 841 src = ReinterpretPathForChroot(src) |
829 | 842 |
830 return RunCommand(['sudo', | 843 return RunCommandCaptureOutput(['sudo', |
831 './start_devserver', | 844 './start_devserver', |
832 '--pregenerate_update', | 845 '--pregenerate_update', |
833 '--exit', | 846 '--exit', |
834 '--image=%s' % target, | 847 '--image=%s' % target, |
835 '--src_image=%s' % src, | 848 '--src_image=%s' % src, |
836 '--for_vm', | 849 '--for_vm', |
837 ], redirect_stdout=True, enter_chroot=True, | 850 ], combine_stdout_stderr=True, |
838 print_cmd=False) | 851 enter_chroot=True, |
| 852 print_cmd=False) |
839 | 853 |
840 # Get the list of deltas by mocking out update method in test class. | 854 # Get the list of deltas by mocking out update method in test class. |
841 test_suite = _PrepareTestSuite(parser, options, GenerateVirtualAUDeltasTest) | 855 test_suite = _PrepareTestSuite(parser, options, GenerateVirtualAUDeltasTest) |
842 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite) | 856 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite) |
843 | 857 |
844 Info('The following delta updates are required.') | 858 Info('The following delta updates are required.') |
845 update_ids = [] | 859 update_ids = [] |
846 jobs = [] | 860 jobs = [] |
847 args = [] | 861 args = [] |
848 for target, srcs in GenerateVirtualAUDeltasTest.delta_list.items(): | 862 for target, srcs in GenerateVirtualAUDeltasTest.delta_list.items(): |
849 for src in srcs: | 863 for src in srcs: |
850 update_id = _GenerateUpdateId(target=target, src=src) | 864 update_id = _GenerateUpdateId(target=target, src=src) |
851 print >> sys.stderr, 'AU: %s' % update_id | 865 print >> sys.stderr, 'AU: %s' % update_id |
852 update_ids.append(update_id) | 866 update_ids.append(update_id) |
853 jobs.append(_GenerateVMUpdate) | 867 jobs.append(_GenerateVMUpdate) |
854 args.append((target, src)) | 868 args.append((target, src)) |
855 | 869 |
856 raw_results = _RunParallelJobs(options.jobs, jobs, args) | 870 raw_results = _RunParallelJobs(options.jobs, jobs, args, print_status=True) |
857 results = [] | 871 results = [] |
858 | 872 |
859 # Parse the output. | 873 # Looking for this line in the output. |
860 key_line_re = re.compile('^PREGENERATED_UPDATE=([\w/.]+)') | 874 key_line_re = re.compile('^PREGENERATED_UPDATE=([\w/.]+)') |
861 for result in raw_results: | 875 for result in raw_results: |
862 for line in result.splitlines(): | 876 (return_code, output, _) = result |
863 match = key_line_re.search(line) | 877 if return_code != 0: |
864 if match: | 878 Warning(output) |
865 # Convert blah/blah/update.gz -> update/blah/blah. | 879 raise UpdateException(return_code, 'Failed to generate all updates.') |
866 path_to_update_gz = match.group(1).rstrip() | 880 else: |
867 (path_to_update_dir, _, _) = path_to_update_gz.rpartition('/update.gz') | 881 for line in output.splitlines(): |
868 results.append('/'.join(['update', path_to_update_dir])) | 882 match = key_line_re.search(line) |
869 break | 883 if match: |
| 884 # Convert blah/blah/update.gz -> update/blah/blah. |
| 885 path_to_update_gz = match.group(1).rstrip() |
| 886 (path_to_update_dir, _, _) = path_to_update_gz.rpartition( |
| 887 '/update.gz') |
| 888 results.append('/'.join(['update', path_to_update_dir])) |
| 889 break |
870 | 890 |
871 assert len(raw_results) == len(results), \ | 891 # Make sure all generation of updates returned cached locations. |
872 'Insufficient number cache directories returned.' | 892 if len(raw_results) != len(results): |
| 893 raise UpdateException(1, 'Insufficient number cache directories returned.') |
873 | 894 |
874 # Build the dictionary from our id's and returned cache paths. | 895 # Build the dictionary from our id's and returned cache paths. |
875 cache_dictionary = {} | 896 cache_dictionary = {} |
876 for index, id in enumerate(update_ids): | 897 for index, id in enumerate(update_ids): |
877 cache_dictionary[id] = results[index] | 898 cache_dictionary[id] = results[index] |
878 | 899 |
879 return cache_dictionary | 900 return cache_dictionary |
880 | 901 |
881 | 902 |
882 def _RunTestsInParallel(parser, options, test_class): | 903 def _RunTestsInParallel(parser, options, test_class): |
883 """Runs the tests given by the options and test_class in parallel.""" | 904 """Runs the tests given by the options and test_class in parallel.""" |
884 threads = [] | 905 threads = [] |
885 args = [] | 906 args = [] |
886 test_suite = _PrepareTestSuite(parser, options, test_class) | 907 test_suite = _PrepareTestSuite(parser, options, test_class) |
887 for test in test_suite: | 908 for test in test_suite: |
888 test_name = test.id() | 909 test_name = test.id() |
889 test_case = unittest.TestLoader().loadTestsFromName(test_name) | 910 test_case = unittest.TestLoader().loadTestsFromName(test_name) |
890 threads.append(unittest.TextTestRunner().run) | 911 threads.append(unittest.TextTestRunner().run) |
891 args.append(test_case) | 912 args.append(test_case) |
892 | 913 |
893 results = _RunParallelJobs(options.jobs, threads, args) | 914 results = _RunParallelJobs(options.jobs, threads, args, print_status=False) |
894 if not (test_result.wasSuccessful() for test_result in results): | 915 if not (test_result.wasSuccessful() for test_result in results): |
895 Die('Test harness was not successful') | 916 Die('Test harness was not successful') |
896 | 917 |
897 | 918 |
898 def main(): | 919 def main(): |
899 parser = optparse.OptionParser() | 920 parser = optparse.OptionParser() |
900 parser.add_option('-b', '--base_image', | 921 parser.add_option('-b', '--base_image', |
901 help='path to the base image.') | 922 help='path to the base image.') |
902 parser.add_option('-r', '--board', | 923 parser.add_option('-r', '--board', |
903 help='board for the images.') | 924 help='board for the images.') |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
946 else: | 967 else: |
947 dev_server_cache = None | 968 dev_server_cache = None |
948 test_suite = _PrepareTestSuite(parser, options, test_class) | 969 test_suite = _PrepareTestSuite(parser, options, test_class) |
949 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) | 970 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) |
950 if not test_result.wasSuccessful(): | 971 if not test_result.wasSuccessful(): |
951 Die('Test harness was not successful.') | 972 Die('Test harness was not successful.') |
952 | 973 |
953 | 974 |
954 if __name__ == '__main__': | 975 if __name__ == '__main__': |
955 main() | 976 main() |
OLD | NEW |