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 |