| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2006-2009 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 """Run layout tests using the test_shell. | 6 """Run layout tests using the test_shell. |
| 7 | 7 |
| 8 This is a port of the existing webkit test script run-webkit-tests. | 8 This is a port of the existing webkit test script run-webkit-tests. |
| 9 | 9 |
| 10 The TestRunner class runs a series of tests (TestType interface) against a set | 10 The TestRunner class runs a series of tests (TestType interface) against a set |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 61 self.timeout = timeout | 61 self.timeout = timeout |
| 62 expected_hash_file = path_utils.ExpectedFilename(filename, '.checksum') | 62 expected_hash_file = path_utils.ExpectedFilename(filename, '.checksum') |
| 63 try: | 63 try: |
| 64 self.image_hash = open(expected_hash_file, "r").read() | 64 self.image_hash = open(expected_hash_file, "r").read() |
| 65 except IOError, e: | 65 except IOError, e: |
| 66 if errno.ENOENT != e.errno: | 66 if errno.ENOENT != e.errno: |
| 67 raise | 67 raise |
| 68 self.image_hash = None | 68 self.image_hash = None |
| 69 | 69 |
| 70 | 70 |
| 71 class ResultSummaryEntry: |
| 72 def __init__(self, all, failed, failure_counts, skipped): |
| 73 """Resolves result counts. |
| 74 |
| 75 Args: |
| 76 all: list of all tests in this category |
| 77 failed: list of failing tests in this category |
| 78 skipped: list of skipped tests |
| 79 failure_counts: dictionary of (TestFailure -> frequency) |
| 80 """ |
| 81 self.skip_count = len(skipped) |
| 82 self.total_count = len(all | skipped) |
| 83 self.pass_count = self.total_count - self.skip_count - len(failed) |
| 84 self.failure_counts = failure_counts; |
| 85 |
| 86 |
| 87 class ResultSummary: |
| 88 """Container for all result summaries for this test run. |
| 89 |
| 90 Args: |
| 91 deferred: ResultSummary object for deferred tests. |
| 92 non_wontfix: ResultSummary object for non_wontfix tests. |
| 93 all: ResultSummary object for all tests, including skipped tests. |
| 94 fixable_count: Count of all fixable and skipped tests. This is essentially |
| 95 a deduped sum of all the non_wontfix failure counts. |
| 96 """ |
| 97 def __init__(self, deferred, non_wontfix, all, fixable_count): |
| 98 self.deferred = deferred |
| 99 self.non_wontfix = non_wontfix |
| 100 self.all = all |
| 101 self.fixable_count = fixable_count |
| 102 |
| 103 |
| 71 class TestRunner: | 104 class TestRunner: |
| 72 """A class for managing running a series of tests on a series of test | 105 """A class for managing running a series of tests on a series of test |
| 73 files.""" | 106 files.""" |
| 74 | 107 |
| 75 # When collecting test cases, we include any file with these extensions. | 108 # When collecting test cases, we include any file with these extensions. |
| 76 _supported_file_extensions = set(['.html', '.shtml', '.xml', '.xhtml', '.pl', | 109 _supported_file_extensions = set(['.html', '.shtml', '.xml', '.xhtml', '.pl', |
| 77 '.php', '.svg']) | 110 '.php', '.svg']) |
| 78 # When collecting test cases, skip these directories | 111 # When collecting test cases, skip these directories |
| 79 _skipped_directories = set(['.svn', '_svn', 'resources', 'script-tests']) | 112 _skipped_directories = set(['.svn', '_svn', 'resources', 'script-tests']) |
| 80 | 113 |
| (...skipping 480 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 561 self._PrintTimingStatistics(test_timings, individual_test_timings, failures) | 594 self._PrintTimingStatistics(test_timings, individual_test_timings, failures) |
| 562 | 595 |
| 563 print "-" * 78 | 596 print "-" * 78 |
| 564 | 597 |
| 565 # Tests are done running. Compare failures with expected failures. | 598 # Tests are done running. Compare failures with expected failures. |
| 566 regressions = self._CompareFailures(failures) | 599 regressions = self._CompareFailures(failures) |
| 567 | 600 |
| 568 print "-" * 78 | 601 print "-" * 78 |
| 569 | 602 |
| 570 # Write summaries to stdout. | 603 # Write summaries to stdout. |
| 571 self._PrintResults(failures, sys.stdout) | 604 result_summary = self._GetResultSummary(failures) |
| 605 self._PrintResultSummary(result_summary, sys.stdout) |
| 572 | 606 |
| 573 if self._options.verbose: | 607 if self._options.verbose: |
| 574 self._WriteJSONFiles(failures, individual_test_timings); | 608 self._WriteJSONFiles(failures, individual_test_timings, result_summary); |
| 575 | 609 |
| 576 # Write the same data to a log file. | 610 # Write the same data to a log file. |
| 577 out_filename = os.path.join(self._options.results_directory, "score.txt") | 611 out_filename = os.path.join(self._options.results_directory, "score.txt") |
| 578 output_file = open(out_filename, "w") | 612 output_file = open(out_filename, "w") |
| 579 self._PrintResults(failures, output_file) | 613 self._PrintResultSummary(result_summary, output_file) |
| 580 output_file.close() | 614 output_file.close() |
| 581 | 615 |
| 582 # Write the summary to disk (results.html) and maybe open the test_shell | 616 # Write the summary to disk (results.html) and maybe open the test_shell |
| 583 # to this file. | 617 # to this file. |
| 584 wrote_results = self._WriteResultsHtmlFile(failures, regressions) | 618 wrote_results = self._WriteResultsHtmlFile(failures, regressions) |
| 585 if not self._options.noshow_results and wrote_results: | 619 if not self._options.noshow_results and wrote_results: |
| 586 self._ShowResultsHtmlFile() | 620 self._ShowResultsHtmlFile() |
| 587 | 621 |
| 588 sys.stdout.flush() | 622 sys.stdout.flush() |
| 589 sys.stderr.flush() | 623 sys.stderr.flush() |
| 590 return len(regressions) | 624 return len(regressions) |
| 591 | 625 |
| 592 def _WriteJSONFiles(self, failures, individual_test_timings): | 626 def _WriteJSONFiles(self, failures, individual_test_timings, result_summary): |
| 593 logging.debug("Writing JSON files in %s." % self._options.results_directory) | 627 logging.debug("Writing JSON files in %s." % self._options.results_directory) |
| 594 # Write a json file of the test_expectations.txt file for the layout tests | 628 # Write a json file of the test_expectations.txt file for the layout tests |
| 595 # dashboard. | 629 # dashboard. |
| 596 expectations_file = open(os.path.join(self._options.results_directory, | 630 expectations_file = open(os.path.join(self._options.results_directory, |
| 597 "expectations.json"), "w") | 631 "expectations.json"), "w") |
| 598 expectations_json = self._expectations.GetExpectationsJsonForAllPlatforms() | 632 expectations_json = self._expectations.GetExpectationsJsonForAllPlatforms() |
| 599 expectations_file.write(("ADD_EXPECTATIONS(" + expectations_json + ");")) | 633 expectations_file.write(("ADD_EXPECTATIONS(" + expectations_json + ");")) |
| 600 expectations_file.close() | 634 expectations_file.close() |
| 601 | 635 |
| 602 results_file_path = os.path.join(self._options.results_directory, | 636 results_file_path = os.path.join(self._options.results_directory, |
| 603 "results.json") | 637 "results.json") |
| 604 builder_name = self._options.builder_name # "WebKitBuilder" | 638 builder_name = self._options.builder_name # "WebKitBuilder" |
| 605 build_number = self._options.build_number # "12346" | 639 build_number = self._options.build_number # "12346" |
| 606 json_generator = json_results_generator.JSONResultsGenerator(failures, | 640 json_generator = json_results_generator.JSONResultsGenerator(failures, |
| 607 individual_test_timings, builder_name, build_number, | 641 individual_test_timings, builder_name, build_number, |
| 608 results_file_path, self._test_files_list) | 642 results_file_path, self._test_files_list, result_summary) |
| 609 results_json = json_generator.GetJSON() | 643 results_json = json_generator.GetJSON() |
| 610 | 644 |
| 611 results_file = open(results_file_path, "w") | 645 results_file = open(results_file_path, "w") |
| 612 results_file.write(results_json) | 646 results_file.write(results_json) |
| 613 results_file.close() | 647 results_file.close() |
| 614 | 648 |
| 615 logging.debug("Finished writing JSON files.") | 649 logging.debug("Finished writing JSON files.") |
| 616 | 650 |
| 617 def _PrintTimingStatistics(self, directory_test_timings, | 651 def _PrintTimingStatistics(self, directory_test_timings, |
| 618 individual_test_timings, failures): | 652 individual_test_timings, failures): |
| (...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 740 mean = sum(timings) / num_tests | 774 mean = sum(timings) / num_tests |
| 741 | 775 |
| 742 for time in timings: | 776 for time in timings: |
| 743 sum_of_deviations = math.pow(time - mean, 2) | 777 sum_of_deviations = math.pow(time - mean, 2) |
| 744 | 778 |
| 745 std_deviation = math.sqrt(sum_of_deviations / num_tests) | 779 std_deviation = math.sqrt(sum_of_deviations / num_tests) |
| 746 logging.debug(("Median: %s, Mean: %s, 90th percentile: %s, " | 780 logging.debug(("Median: %s, Mean: %s, 90th percentile: %s, " |
| 747 "99th percentile: %s, Standard deviation: %s\n" % ( | 781 "99th percentile: %s, Standard deviation: %s\n" % ( |
| 748 median, mean, percentile90, percentile99, std_deviation))) | 782 median, mean, percentile90, percentile99, std_deviation))) |
| 749 | 783 |
| 750 def _PrintResults(self, failures, output): | 784 def _GetResultSummary(self, failures): |
| 751 """Print a short summary to stdout about how many tests passed. | 785 """Returns a ResultSummary object with failure counts. |
| 752 | 786 |
| 753 Args: | 787 Args: |
| 754 failures is a dictionary mapping the test filename to a list of | 788 failures: dictionary mapping the test filename to a list of |
| 755 TestFailure objects if the test failed | 789 TestFailure objects if the test failed |
| 756 | |
| 757 output is the file descriptor to write the results to. For example, | |
| 758 sys.stdout. | |
| 759 """ | 790 """ |
| 760 | |
| 761 failure_counts = {} | 791 failure_counts = {} |
| 762 deferred_counts = {} | 792 deferred_counts = {} |
| 763 fixable_counts = {} | 793 fixable_counts = {} |
| 764 non_ignored_counts = {} | 794 non_wontfix_counts = {} |
| 765 fixable_failures = set() | 795 fixable_failures = set() |
| 766 deferred_failures = set() | 796 deferred_failures = set() |
| 767 non_ignored_failures = set() | 797 non_wontfix_failures = set() |
| 768 | 798 |
| 769 # Aggregate failures in a dictionary (TestFailure -> frequency), | 799 # Aggregate failures in a dictionary (TestFailure -> frequency), |
| 770 # with known (fixable and ignored) failures separated out. | 800 # with known (fixable and wontfix) failures separated out. |
| 771 def AddFailure(dictionary, key): | 801 def AddFailure(dictionary, key): |
| 772 if key in dictionary: | 802 if key in dictionary: |
| 773 dictionary[key] += 1 | 803 dictionary[key] += 1 |
| 774 else: | 804 else: |
| 775 dictionary[key] = 1 | 805 dictionary[key] = 1 |
| 776 | 806 |
| 777 for test, failures in failures.iteritems(): | 807 for test, failures in failures.iteritems(): |
| 778 for failure in failures: | 808 for failure in failures: |
| 779 AddFailure(failure_counts, failure.__class__) | 809 AddFailure(failure_counts, failure.__class__) |
| 780 if self._expectations.IsDeferred(test): | 810 if self._expectations.IsDeferred(test): |
| 781 AddFailure(deferred_counts, failure.__class__) | 811 AddFailure(deferred_counts, failure.__class__) |
| 782 deferred_failures.add(test) | 812 deferred_failures.add(test) |
| 783 else: | 813 else: |
| 784 if self._expectations.IsFixable(test): | 814 if self._expectations.IsFixable(test): |
| 785 AddFailure(fixable_counts, failure.__class__) | 815 AddFailure(fixable_counts, failure.__class__) |
| 786 fixable_failures.add(test) | 816 fixable_failures.add(test) |
| 787 if not self._expectations.IsIgnored(test): | 817 if not self._expectations.IsIgnored(test): |
| 788 AddFailure(non_ignored_counts, failure.__class__) | 818 AddFailure(non_wontfix_counts, failure.__class__) |
| 789 non_ignored_failures.add(test) | 819 non_wontfix_failures.add(test) |
| 790 | 820 |
| 791 # Print breakdown of tests we need to fix and want to pass. | 821 # Print breakdown of tests we need to fix and want to pass. |
| 792 # Include skipped fixable tests in the statistics. | 822 # Include skipped fixable tests in the statistics. |
| 793 skipped = (self._expectations.GetSkipped() - | 823 skipped = (self._expectations.GetSkipped() - |
| 794 self._expectations.GetDeferredSkipped()) | 824 self._expectations.GetDeferredSkipped()) |
| 795 | 825 |
| 796 self._PrintResultSummary("=> Tests to be fixed for the current release", | 826 deferred_tests = self._expectations.GetDeferred() |
| 797 self._expectations.GetFixable(), | 827 non_wontfix_result_summary = ResultSummaryEntry( |
| 798 fixable_failures, | 828 (self._test_files - self._expectations.GetWontFix() - deferred_tests), |
| 799 fixable_counts, | 829 non_wontfix_failures, |
| 800 skipped, | 830 non_wontfix_counts, |
| 801 output) | 831 skipped) |
| 802 | 832 |
| 803 self._PrintResultSummary("=> Tests we want to pass for the current release", | 833 deferred_result_summary = ResultSummaryEntry( |
| 804 (self._test_files - | 834 deferred_tests, |
| 805 self._expectations.GetWontFix() - | 835 deferred_failures, |
| 806 self._expectations.GetDeferred()), | 836 deferred_counts, |
| 807 non_ignored_failures, | 837 self._expectations.GetDeferredSkipped()) |
| 808 non_ignored_counts, | |
| 809 skipped, | |
| 810 output) | |
| 811 | 838 |
| 812 self._PrintResultSummary("=> Tests to be fixed for a future release", | 839 skipped |= self._expectations.GetWontFixSkipped() |
| 813 self._expectations.GetDeferred(), | 840 all_result_summary = ResultSummaryEntry( |
| 814 deferred_failures, | 841 self._test_files, |
| 815 deferred_counts, | 842 failures, |
| 816 self._expectations.GetDeferredSkipped(), | 843 failure_counts, |
| 817 output) | 844 skipped) |
| 845 |
| 846 total_fixable_count = len(self._expectations.GetFixable() | skipped) |
| 847 |
| 848 return ResultSummary(deferred_result_summary, non_wontfix_result_summary, |
| 849 all_result_summary, total_fixable_count) |
| 850 |
| 851 def _PrintResultSummary(self, result_summary, output): |
| 852 """Print a short summary to stdout about how many tests passed. |
| 853 |
| 854 Args: |
| 855 result_summary: ResultSummary object with failure counts. |
| 856 output: file descriptor to write the results to. For example, sys.stdout. |
| 857 """ |
| 858 output.write( |
| 859 "\nTotal expected failures: %s\n" % result_summary.fixable_count) |
| 860 |
| 861 self._PrintResultSummaryEntry( |
| 862 "Tests we want to pass for the current release", |
| 863 result_summary.non_wontfix, |
| 864 output) |
| 865 |
| 866 self._PrintResultSummaryEntry("Tests to be fixed for a future release", |
| 867 result_summary.deferred, |
| 868 output) |
| 818 | 869 |
| 819 # Print breakdown of all tests including all skipped tests. | 870 # Print breakdown of all tests including all skipped tests. |
| 820 skipped |= self._expectations.GetWontFixSkipped() | 871 self._PrintResultSummaryEntry("All tests", |
| 821 self._PrintResultSummary("=> All tests", | 872 result_summary.all, |
| 822 self._test_files, | 873 output) |
| 823 failures, | |
| 824 failure_counts, | |
| 825 skipped, | |
| 826 output) | |
| 827 print | 874 print |
| 828 | 875 |
| 829 def _PrintResultSummary(self, heading, all, failed, failure_counts, skipped, | 876 def _PrintResultSummaryEntry(self, heading, result_summary, output): |
| 830 output): | |
| 831 """Print a summary block of results for a particular category of test. | 877 """Print a summary block of results for a particular category of test. |
| 832 | 878 |
| 833 Args: | 879 Args: |
| 834 heading: text to print before the block, followed by the total count | 880 heading: text to print before the block, followed by the total count |
| 835 all: list of all tests in this category | 881 result_summary: ResultSummaryEntry object with the result counts |
| 836 failed: list of failing tests in this category | |
| 837 failure_counts: dictionary of (TestFailure -> frequency) | |
| 838 output: file descriptor to write the results to | 882 output: file descriptor to write the results to |
| 839 """ | 883 """ |
| 840 total = len(all | skipped) | 884 output.write("\n=> %s (%d):\n" % (heading, result_summary.total_count)) |
| 841 output.write("\n%s (%d):\n" % (heading, total)) | 885 self._PrintResultLine(result_summary.pass_count, result_summary.total_count, |
| 842 skip_count = len(skipped) | 886 "Passed", output) |
| 843 pass_count = total - skip_count - len(failed) | 887 self._PrintResultLine(result_summary.skip_count, result_summary.total_count, |
| 844 self._PrintResultLine(pass_count, total, "Passed", output) | 888 "Skipped", output) |
| 845 self._PrintResultLine(skip_count, total, "Skipped", output) | 889 sorted_keys = sorted(result_summary.failure_counts.keys(), |
| 846 # Sort the failure counts and print them one by one. | |
| 847 sorted_keys = sorted(failure_counts.keys(), | |
| 848 key=test_failures.FailureSort.SortOrder) | 890 key=test_failures.FailureSort.SortOrder) |
| 849 for failure in sorted_keys: | 891 for failure in sorted_keys: |
| 850 self._PrintResultLine(failure_counts[failure], total, failure.Message(), | 892 self._PrintResultLine(result_summary.failure_counts[failure], |
| 893 result_summary.total_count, |
| 894 failure.Message(), |
| 851 output) | 895 output) |
| 852 | 896 |
| 853 def _PrintResultLine(self, count, total, message, output): | 897 def _PrintResultLine(self, count, total, message, output): |
| 854 if count == 0: return | 898 if count == 0: return |
| 855 output.write( | 899 output.write( |
| 856 ("%(count)d test case%(plural)s (%(percent).1f%%) %(message)s\n" % | 900 ("%(count)d test case%(plural)s (%(percent).1f%%) %(message)s\n" % |
| 857 { 'count' : count, | 901 { 'count' : count, |
| 858 'plural' : ('s', '')[count == 1], | 902 'plural' : ('s', '')[count == 1], |
| 859 'percent' : float(count) * 100 / total, | 903 'percent' : float(count) * 100 / total, |
| 860 'message' : message })) | 904 'message' : message })) |
| (...skipping 328 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1189 option_parser.add_option("", "--build-number", | 1233 option_parser.add_option("", "--build-number", |
| 1190 default="DUMMY_BUILD_NUMBER", | 1234 default="DUMMY_BUILD_NUMBER", |
| 1191 help=("The build number of the builder running" | 1235 help=("The build number of the builder running" |
| 1192 "this script.")) | 1236 "this script.")) |
| 1193 option_parser.add_option("", "--find-baselines", action="store_true", | 1237 option_parser.add_option("", "--find-baselines", action="store_true", |
| 1194 default=False, | 1238 default=False, |
| 1195 help="Prints a table mapping tests to their " | 1239 help="Prints a table mapping tests to their " |
| 1196 "expected results") | 1240 "expected results") |
| 1197 options, args = option_parser.parse_args() | 1241 options, args = option_parser.parse_args() |
| 1198 main(options, args) | 1242 main(options, args) |
| OLD | NEW |