| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Runs each test cases as a single shard, single process execution. | 6 """Runs each test cases as a single shard, single process execution. |
| 7 | 7 |
| 8 Similar to sharding_supervisor.py but finer grained. It runs each test case | 8 Similar to sharding_supervisor.py but finer grained. It runs each test case |
| 9 individually instead of running per shard. Runs multiple instances in parallel. | 9 individually instead of running per shard. Runs multiple instances in parallel. |
| 10 """ | 10 """ |
| (...skipping 725 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 736 test_case_data['duration'] = float(match.group(1)) / 1000. | 736 test_case_data['duration'] = float(match.group(1)) / 1000. |
| 737 test_case_data['returncode'] = int(line.startswith(FAILED_PREFIX)) | 737 test_case_data['returncode'] = int(line.startswith(FAILED_PREFIX)) |
| 738 yield test_case_data | 738 yield test_case_data |
| 739 test_case = None | 739 test_case = None |
| 740 test_case_data = None | 740 test_case_data = None |
| 741 | 741 |
| 742 if test_case_data: | 742 if test_case_data: |
| 743 assert not terminated_early | 743 assert not terminated_early |
| 744 # This means the last one likely crashed. | 744 # This means the last one likely crashed. |
| 745 test_case_data['crashed'] = True | 745 test_case_data['crashed'] = True |
| 746 test_case_data['duration'] = 0 |
| 746 test_case_data['returncode'] = 1 | 747 test_case_data['returncode'] = 1 |
| 747 yield test_case_data | 748 yield test_case_data |
| 748 elif terminated_early: | 749 elif terminated_early: |
| 749 for _ in lines: | 750 for _ in lines: |
| 750 # Exhaust the generator. | 751 # Exhaust the generator. |
| 751 pass | 752 pass |
| 752 | 753 |
| 753 # If test_cases is not empty, these test cases were not run. | 754 # If test_cases is not empty, these test cases were not run. |
| 754 for t in test_cases: | 755 for t in test_cases: |
| 755 yield { | 756 yield { |
| (...skipping 27 matching lines...) Expand all Loading... |
| 783 # that ran if the startup cost was above 10ms. | 784 # that ran if the startup cost was above 10ms. |
| 784 if startup_duration > 0.01 and data_ran: | 785 if startup_duration > 0.01 and data_ran: |
| 785 distributed_duration = startup_duration / len(data_ran) | 786 distributed_duration = startup_duration / len(data_ran) |
| 786 for i in data_ran: | 787 for i in data_ran: |
| 787 i['duration'] += distributed_duration | 788 i['duration'] += distributed_duration |
| 788 i['returncode'] = returncode or i['returncode'] | 789 i['returncode'] = returncode or i['returncode'] |
| 789 break | 790 break |
| 790 return data | 791 return data |
| 791 | 792 |
| 792 | 793 |
| 794 def convert_to_lines(generator): |
| 795 """Turn input coming from a generator into lines. |
| 796 |
| 797 It is Windows-friendly. |
| 798 """ |
| 799 accumulator = '' |
| 800 for data in generator: |
| 801 items = (accumulator + data).splitlines(True) |
| 802 for item in items[:-1]: |
| 803 yield item |
| 804 if items[-1].endswith(('\r', '\n')): |
| 805 yield items[-1] |
| 806 accumulator = '' |
| 807 else: |
| 808 accumulator = items[-1] |
| 809 if accumulator: |
| 810 yield accumulator |
| 811 |
| 812 |
| 793 def chromium_filter_tests(data): | 813 def chromium_filter_tests(data): |
| 794 """Removes funky PRE_ chromium-specific tests.""" | 814 """Returns a generator that removes funky PRE_ chromium-specific tests.""" |
| 795 return [d for d in data if chromium_valid(d['test_case'], False, True)] | 815 return (d for d in data if chromium_valid(d['test_case'], False, True)) |
| 796 | 816 |
| 797 | 817 |
| 798 class Runner(object): | 818 class Runner(object): |
| 799 """Immutable settings to run many test cases in a loop.""" | 819 """Immutable settings to run many test cases in a loop.""" |
| 800 def __init__( | 820 def __init__( |
| 801 self, cmd, cwd_dir, timeout, progress, retries, decider, verbose, | 821 self, cmd, cwd_dir, timeout, progress, retries, decider, verbose, |
| 802 add_task, add_serial_task): | 822 add_task, add_serial_task): |
| 803 self.cmd = cmd[:] | 823 self.cmd = cmd[:] |
| 804 self.cwd_dir = cwd_dir | 824 self.cwd_dir = cwd_dir |
| 805 self.timeout = timeout | 825 self.timeout = timeout |
| (...skipping 21 matching lines...) Expand all Loading... |
| 827 if '--gtest_print_time' not in cmd: | 847 if '--gtest_print_time' not in cmd: |
| 828 cmd.append('--gtest_print_time') | 848 cmd.append('--gtest_print_time') |
| 829 | 849 |
| 830 # TODO(maruel): Use a distribution model. | 850 # TODO(maruel): Use a distribution model. |
| 831 timeout = self.timeout * len(test_cases) | 851 timeout = self.timeout * len(test_cases) |
| 832 | 852 |
| 833 if self.verbose > 1: | 853 if self.verbose > 1: |
| 834 self.progress.update_item('Starting command %s with a timeout of %ss' % | 854 self.progress.update_item('Starting command %s with a timeout of %ss' % |
| 835 (cmd, timeout), False, False) | 855 (cmd, timeout), False, False) |
| 836 | 856 |
| 837 output, _, returncode, duration = call_with_timeout( | 857 # TODO(maruel): Differentiate between soft and hard timeouts. |
| 858 proc = Popen( |
| 838 cmd, | 859 cmd, |
| 839 timeout, | |
| 840 cwd=self.cwd_dir, | 860 cwd=self.cwd_dir, |
| 861 stdout=subprocess.PIPE, |
| 841 stderr=subprocess.STDOUT, | 862 stderr=subprocess.STDOUT, |
| 842 env=self.env) | 863 env=self.env) |
| 843 | 864 # Create a pipeline of generators. |
| 844 if self.verbose > 1: | 865 gen_lines = convert_to_lines(data for _, data in proc.yield_any(timeout)) |
| 845 self.progress.update_item('Command %s finished after %ss' % (cmd, | |
| 846 duration), | |
| 847 False, False) | |
| 848 | |
| 849 # It needs to be valid utf-8 otherwise it can't be stored. | 866 # It needs to be valid utf-8 otherwise it can't be stored. |
| 850 # TODO(maruel): Be more intelligent than decoding to ascii. | 867 # TODO(maruel): Be more intelligent than decoding to ascii. |
| 851 utf8_output = output.decode('ascii', 'ignore').encode('utf-8') | 868 gen_lines_utf8 = ( |
| 869 line.decode('ascii', 'ignore').encode('utf-8') for line in gen_lines) |
| 870 gen_test_cases = process_output(gen_lines_utf8, test_cases) |
| 871 gen_test_cases_filtered = chromium_filter_tests(gen_test_cases) |
| 852 | 872 |
| 853 data = process_output(utf8_output.splitlines(True), test_cases) | 873 data = [] |
| 854 data = normalize_testing_time(data, duration, returncode) | 874 for i in gen_test_cases_filtered: |
| 855 data = chromium_filter_tests(data) | 875 if i['duration'] is None: |
| 856 | 876 continue |
| 857 if sys.platform == 'win32': | 877 # A new test_case completed. |
| 858 output = output.replace('\r\n', '\n') | 878 data.append(i) |
| 859 | |
| 860 for i in data: | |
| 861 self.decider.got_result(i['returncode'] == 0) | 879 self.decider.got_result(i['returncode'] == 0) |
| 862 need_to_retry = i['returncode'] != 0 and try_count < self.retries | 880 need_to_retry = i['returncode'] != 0 and try_count < self.retries |
| 863 if try_count: | 881 if try_count: |
| 864 line = '%s (%.2fs) - retry #%d' % ( | 882 line = '%s (%.2fs) - retry #%d' % ( |
| 865 i['test_case'], i['duration'] or 0, try_count) | 883 i['test_case'], i['duration'] or 0, try_count) |
| 866 else: | 884 else: |
| 867 line = '%s (%.2fs)' % (i['test_case'], i['duration'] or 0) | 885 line = '%s (%.2fs)' % (i['test_case'], i['duration'] or 0) |
| 868 if self.verbose or i['returncode'] != 0 or try_count > 0: | 886 if self.verbose or i['returncode'] != 0 or try_count > 0: |
| 869 # Print output in one of three cases: | 887 # Print output in one of three cases: |
| 870 # --verbose was specified. | 888 # --verbose was specified. |
| 871 # The test failed. | 889 # The test failed. |
| 872 # The wasn't the first attempt (this is needed so the test parser can | 890 # The wasn't the first attempt (this is needed so the test parser can |
| 873 # detect that a test has been successfully retried). | 891 # detect that a test has been successfully retried). |
| 874 if i['output']: | 892 if i['output']: |
| 875 line += '\n' + i['output'] | 893 line += '\n' + i['output'] |
| 876 self.progress.update_item(line, True, need_to_retry) | 894 self.progress.update_item(line, True, need_to_retry) |
| 877 | 895 |
| 878 if need_to_retry: | 896 if need_to_retry: |
| 879 if try_count + 1 < self.retries: | 897 if try_count + 1 < self.retries: |
| 880 # The test failed and needs to be retried normally. | 898 # The test failed and needs to be retried normally. |
| 881 # Leave a buffer of ~40 test cases before retrying. | 899 # Leave a buffer of ~40 test cases before retrying. |
| 882 priority += 40 | 900 priority += 40 |
| 883 self.add_task( | 901 self.add_task( |
| 884 priority, self.map, priority, [i['test_case']], try_count + 1) | 902 priority, self.map, priority, [i['test_case']], try_count + 1) |
| 885 else: | 903 else: |
| 886 # This test only has one retry left, so the final retry should be | 904 # This test only has one retry left, so the final retry should be |
| 887 # done serially. | 905 # done serially. |
| 888 self.add_serial_task( | 906 self.add_serial_task( |
| 889 priority, self.map, priority, [i['test_case']], try_count + 1) | 907 priority, self.map, priority, [i['test_case']], try_count + 1) |
| 890 return data | 908 |
| 909 if self.verbose > 1: |
| 910 self.progress.update_item( |
| 911 'Command %s finished after %ss' % (cmd, proc.duration()), |
| 912 False, False) |
| 913 |
| 914 return normalize_testing_time(data, proc.duration(), proc.returncode) |
| 891 | 915 |
| 892 | 916 |
| 893 def get_test_cases( | 917 def get_test_cases( |
| 894 cmd, cwd, whitelist, blacklist, index, shards, seed, disabled, fails, flaky, | 918 cmd, cwd, whitelist, blacklist, index, shards, seed, disabled, fails, flaky, |
| 895 manual): | 919 manual): |
| 896 """Returns the filtered list of test cases. | 920 """Returns the filtered list of test cases. |
| 897 | 921 |
| 898 This is done synchronously. | 922 This is done synchronously. |
| 899 """ | 923 """ |
| 900 try: | 924 try: |
| (...skipping 670 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1571 options.gtest_output, | 1595 options.gtest_output, |
| 1572 result_file, | 1596 result_file, |
| 1573 options.verbose) | 1597 options.verbose) |
| 1574 except Failure as e: | 1598 except Failure as e: |
| 1575 print >> sys.stderr, e.args[0] | 1599 print >> sys.stderr, e.args[0] |
| 1576 return 1 | 1600 return 1 |
| 1577 | 1601 |
| 1578 | 1602 |
| 1579 if __name__ == '__main__': | 1603 if __name__ == '__main__': |
| 1580 sys.exit(main(sys.argv[1:])) | 1604 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |