| OLD | NEW |
| 1 #!/bin/env python | 1 #!/bin/env python |
| 2 # Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2006-2008 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 # purify_analyze.py | 6 # purify_analyze.py |
| 7 | 7 |
| 8 ''' Given a Purify text file, parses messages, normalizes and uniques them. | 8 ''' Given a Purify text file, parses messages, normalizes and uniques them. |
| 9 If there's an existing baseline of this data, it can compare against that | 9 If there's an existing baseline of this data, it can compare against that |
| 10 baseline and return an error code if there are any new errors not in the | 10 baseline and return an error code if there are any new errors not in the |
| (...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 156 pat_ignore = (re.compile('^(Start|Exit)ing'), | 156 pat_ignore = (re.compile('^(Start|Exit)ing'), |
| 157 re.compile('^Program terminated'), | 157 re.compile('^Program terminated'), |
| 158 re.compile('^Terminating thread'), | 158 re.compile('^Terminating thread'), |
| 159 re.compile('^Message: CryptVerifySignature')) | 159 re.compile('^Message: CryptVerifySignature')) |
| 160 # message types that aren't analyzed | 160 # message types that aren't analyzed |
| 161 # handled, ignored and continued exceptions will likely never be interesting | 161 # handled, ignored and continued exceptions will likely never be interesting |
| 162 # TODO(erikkay): MPK ("potential" memory leaks) may be worth turning on | 162 # TODO(erikkay): MPK ("potential" memory leaks) may be worth turning on |
| 163 types_excluded = ("EXH", "EXI", "EXC", "MPK") | 163 types_excluded = ("EXH", "EXI", "EXC", "MPK") |
| 164 | 164 |
| 165 | 165 |
| 166 def __init__(self, files, echo, name=None, source_dir=None, data_dir=None): | 166 def __init__(self, files, echo, name=None, source_dir=None, data_dir=None, |
| 167 report_dir=None): |
| 167 # The input file we're analyzing. | 168 # The input file we're analyzing. |
| 168 self._files = files | 169 self._files = files |
| 170 |
| 169 # Whether the input file contents should be echoed to stdout. | 171 # Whether the input file contents should be echoed to stdout. |
| 170 self._echo = echo | 172 self._echo = echo |
| 173 |
| 171 # A symbolic name for the run being analyzed, often the name of the | 174 # A symbolic name for the run being analyzed, often the name of the |
| 172 # exe which was purified. | 175 # exe which was purified. |
| 173 self._name = name | 176 self._name = name |
| 177 |
| 174 # The top of the source code tree of the code we're analyzing. | 178 # The top of the source code tree of the code we're analyzing. |
| 175 # This prefix is stripped from all filenames in stacks for normalization. | 179 # This prefix is stripped from all filenames in stacks for normalization. |
| 176 if source_dir: | 180 if source_dir: |
| 177 purify_message.Stack.SetSourceDir(source_dir) | 181 purify_message.Stack.SetSourceDir(source_dir) |
| 182 |
| 183 script_dir = google.path_utils.ScriptDir() |
| 184 |
| 178 if data_dir: | 185 if data_dir: |
| 179 self._data_dir = data_dir | 186 self._data_dir = data_dir |
| 187 self._global_data_dir = os.path.join(script_dir, "data") |
| 180 else: | 188 else: |
| 181 self._data_dir = os.path.join(google.path_utils.ScriptDir(), "data") | 189 self._data_dir = os.path.join(script_dir, "data") |
| 190 self._global_data_dir = None |
| 191 |
| 192 if report_dir: |
| 193 self._report_dir = report_dir |
| 194 else: |
| 195 self._report_dir = os.path.join(script_dir, "latest") |
| 196 |
| 182 # A map of message_type to a MessageList of that type. | 197 # A map of message_type to a MessageList of that type. |
| 183 self._message_lists = {} | 198 self._message_lists = {} |
| 184 self._ReadIgnoreFile() | 199 self._ReadIgnoreFile() |
| 185 | 200 |
| 186 def _ReadIgnoreFile(self): | 201 def _ReadIgnoreFile(self): |
| 187 '''Read a file which is a list of regexps for either the title or the | 202 '''Read a file which is a list of regexps for either the title or the |
| 188 top-most visible stack line. | 203 top-most visible stack line. |
| 189 ''' | 204 ''' |
| 190 self._pat_ignore = [] | 205 self._pat_ignore = [] |
| 191 filenames = [os.path.join(self._data_dir, "ignore.txt"), | 206 filenames = [os.path.join(self._data_dir, "ignore.txt")] |
| 192 os.path.join(google.path_utils.ScriptDir(), "data", "ignore.txt")] | 207 if self._global_data_dir: |
| 208 filenames.append(os.path.join(self._global_data_dir, "ignore.txt")) |
| 193 for filename in filenames: | 209 for filename in filenames: |
| 194 if os.path.exists(filename): | 210 if os.path.exists(filename): |
| 195 f = open(filename, 'r') | 211 f = open(filename, 'r') |
| 196 for line in f.readlines(): | 212 for line in f.readlines(): |
| 197 if line.startswith("#") or line.startswith("//") or line.isspace(): | 213 if line.startswith("#") or line.startswith("//") or line.isspace(): |
| 198 continue | 214 continue |
| 199 line = line.rstrip() | 215 line = line.rstrip() |
| 200 pat = re.compile(line) | 216 pat = re.compile(line) |
| 201 if pat: | 217 if pat: |
| 202 self._pat_ignore.append(pat) | 218 self._pat_ignore.append(pat) |
| (...skipping 385 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 588 print "test error counts" | 604 print "test error counts" |
| 589 print "========================" | 605 print "========================" |
| 590 tests = test_counts.keys() | 606 tests = test_counts.keys() |
| 591 tests.sort() | 607 tests.sort() |
| 592 for test in tests: | 608 for test in tests: |
| 593 print "%s: %d" % (test, test_counts[test]) | 609 print "%s: %d" % (test, test_counts[test]) |
| 594 # make sure stdout is flushed to avoid weird overlaps with logging | 610 # make sure stdout is flushed to avoid weird overlaps with logging |
| 595 print | 611 print |
| 596 sys.stdout.flush() | 612 sys.stdout.flush() |
| 597 | 613 |
| 598 def SaveLatestStrings(self, string_list, key, fname_extra=""): | 614 def SaveStrings(self, string_list, key, fname_extra=""): |
| 599 '''Output a list of strings to a file in the "latest" dir. | 615 '''Output a list of strings to a file in the report dir. |
| 600 ''' | 616 ''' |
| 601 script_dir = google.path_utils.ScriptDir() | 617 out = os.path.join(self._report_dir, |
| 602 path = os.path.join(script_dir, "latest") | 618 "%s_%s%s.txt" % (self._name, key, fname_extra)) |
| 603 out = os.path.join(path, "%s_%s%s.txt" % (self._name, key, fname_extra)) | |
| 604 logging.info("saving %s" % (out)) | 619 logging.info("saving %s" % (out)) |
| 605 try: | 620 try: |
| 606 f = open(out, "w+") | 621 f = open(out, "w+") |
| 607 f.write('\n'.join(string_list)) | 622 f.write('\n'.join(string_list)) |
| 608 except IOError, (errno, strerror): | 623 except IOError, (errno, strerror): |
| 609 logging.error("error writing to file %s (%d, %s)" % out, errno, strerror) | 624 logging.error("error writing to file %s (%d, %s)" % out, errno, strerror) |
| 610 if f: | 625 if f: |
| 611 f.close() | 626 f.close() |
| 612 return True | 627 return True |
| 613 | 628 |
| 614 def SaveResults(self, path=None, verbose=False): | 629 def SaveResults(self, path=None, verbose=False): |
| 615 ''' Output normalized data to baseline files for future comparison runs. | 630 ''' Output normalized data to baseline files for future comparison runs. |
| 616 Messages are saved in sorted order into a separate file for each message | 631 Messages are saved in sorted order into a separate file for each message |
| 617 type. See Message.NormalizedStr() for details of what's written. | 632 type. See Message.NormalizedStr() for details of what's written. |
| 618 ''' | 633 ''' |
| 619 if not path: | 634 if not path: |
| 620 path = self._data_dir | 635 path = self._report_dir |
| 621 for key in self._message_lists: | 636 for key in self._message_lists: |
| 622 out = os.path.join(path, "%s_%s.txt" % (self._name, key)) | 637 out = os.path.join(path, "%s_%s.txt" % (self._name, key)) |
| 623 logging.info("saving %s" % (out)) | 638 logging.info("saving %s" % (out)) |
| 624 f = open(out, "w+") | 639 f = open(out, "w+") |
| 625 list = self._message_lists[key].UniqueMessages() | 640 list = self._message_lists[key].UniqueMessages() |
| 626 # TODO(erikkay): should the count of each message be a diff factor? | 641 # TODO(erikkay): should the count of each message be a diff factor? |
| 627 # (i.e. the same error shows up, but more frequently) | 642 # (i.e. the same error shows up, but more frequently) |
| 628 for message in list: | 643 for message in list: |
| 629 f.write(message.NormalizedStr(verbose=verbose)) | 644 f.write(message.NormalizedStr(verbose=verbose)) |
| 630 f.write("\n") | 645 f.write("\n") |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 691 msg += line | 706 msg += line |
| 692 else: | 707 else: |
| 693 # note that the hash doesn't include the title, see Message.__hash__ | 708 # note that the hash doesn't include the title, see Message.__hash__ |
| 694 h = hash(msg) | 709 h = hash(msg) |
| 695 msgs[h] = title + msg | 710 msgs[h] = title + msg |
| 696 title = None | 711 title = None |
| 697 msg = "" | 712 msg = "" |
| 698 logging.info("%s: %d msgs" % (filename, len(msgs))) | 713 logging.info("%s: %d msgs" % (filename, len(msgs))) |
| 699 return msgs | 714 return msgs |
| 700 | 715 |
| 701 def _SaveLatestGroupSummary(self, message_list): | 716 def _SaveGroupSummary(self, message_list): |
| 702 '''Save a summary of message groups and their counts to a file in "latest" | 717 '''Save a summary of message groups and their counts to a file in report_dir |
| 703 ''' | 718 ''' |
| 704 string_list = [] | 719 string_list = [] |
| 705 groups = message_list.UniqueMessageGroups() | 720 groups = message_list.UniqueMessageGroups() |
| 706 group_keys = groups.keys() | 721 group_keys = groups.keys() |
| 707 | 722 |
| 708 group_keys.sort(cmp=lambda x,y: len(groups[y]) - len(groups[x])) | 723 group_keys.sort(cmp=lambda x,y: len(groups[y]) - len(groups[x])) |
| 709 for group in group_keys: | 724 for group in group_keys: |
| 710 string_list.append("%s: %d" % (group, len(groups[group]))) | 725 string_list.append("%s: %d" % (group, len(groups[group]))) |
| 711 | 726 |
| 712 self.SaveLatestStrings(string_list, message_list.GetType(), "_GROUPS") | 727 self.SaveStrings(string_list, message_list.GetType(), "_GROUPS") |
| 713 | 728 |
| 714 def CompareResults(self): | 729 def CompareResults(self): |
| 715 ''' Compares the results from the current run with the baseline data | 730 ''' Compares the results from the current run with the baseline data |
| 716 stored in data/<name>_<key>.txt returning False if it finds new errors | 731 stored in data/<name>_<key>.txt returning False if it finds new errors |
| 717 that are not in the baseline. See ReadFile() and SaveResults() for | 732 that are not in the baseline. See ReadFile() and SaveResults() for |
| 718 details of what's in the original file and what's in the baseline. | 733 details of what's in the original file and what's in the baseline. |
| 719 Errors that show up in the baseline but not the current run are not | 734 Errors that show up in the baseline but not the current run are not |
| 720 considered errors (they're considered "fixed"), but they do suggest | 735 considered errors (they're considered "fixed"), but they do suggest |
| 721 that the baseline file could be re-generated.''' | 736 that the baseline file could be re-generated.''' |
| 722 errors = 0 | 737 errors = 0 |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 780 purify_message.GetMessageType(type), type, | 795 purify_message.GetMessageType(type), type, |
| 781 len(type_errors), len(type_fixes))) | 796 len(type_errors), len(type_fixes))) |
| 782 | 797 |
| 783 if len(type_errors): | 798 if len(type_errors): |
| 784 strs = [current_hashes[x].NormalizedStr(verbose=True) | 799 strs = [current_hashes[x].NormalizedStr(verbose=True) |
| 785 for x in type_errors] | 800 for x in type_errors] |
| 786 logging.error("%d new '%s(%s)' errors found\n%s" % (len(type_errors), | 801 logging.error("%d new '%s(%s)' errors found\n%s" % (len(type_errors), |
| 787 purify_message.GetMessageType(type), type, | 802 purify_message.GetMessageType(type), type, |
| 788 '\n'.join(strs))) | 803 '\n'.join(strs))) |
| 789 strs = [current_hashes[x].NormalizedStr() for x in type_errors] | 804 strs = [current_hashes[x].NormalizedStr() for x in type_errors] |
| 790 self.SaveLatestStrings(strs, type, "_NEW") | 805 self.SaveStrings(strs, type, "_NEW") |
| 791 errors += len(type_errors) | 806 errors += len(type_errors) |
| 792 | 807 |
| 793 if len(type_fixes): | 808 if len(type_fixes): |
| 794 # we don't have access to the original message, so all we can do is log | 809 # we don't have access to the original message, so all we can do is log |
| 795 # the non-verbose normalized text | 810 # the non-verbose normalized text |
| 796 logging.warning("%d new '%s(%s)' unexpected fixes found\n%s" % ( | 811 logging.warning("%d new '%s(%s)' unexpected fixes found\n%s" % ( |
| 797 len(type_fixes), purify_message.GetMessageType(type), | 812 len(type_fixes), purify_message.GetMessageType(type), |
| 798 type, '\n'.join(type_fixes))) | 813 type, '\n'.join(type_fixes))) |
| 799 self.SaveLatestStrings(type_fixes, type, "_FIXED") | 814 self.SaveStrings(type_fixes, type, "_FIXED") |
| 800 fixes += len(type_fixes) | 815 fixes += len(type_fixes) |
| 801 if len(current_messages) == 0: | 816 if len(current_messages) == 0: |
| 802 logging.warning("all errors fixed in %s" % baseline_file) | 817 logging.warning("all errors fixed in %s" % baseline_file) |
| 803 | 818 |
| 804 if len(type_fixes) or len(type_errors): | 819 if len(type_fixes) or len(type_errors): |
| 805 strs = [baseline_hashes[x] for x in new_baseline] | 820 strs = [baseline_hashes[x] for x in new_baseline] |
| 806 self.SaveLatestStrings(strs, type, "_BASELINE") | 821 self.SaveStrings(strs, type, "_BASELINE") |
| 807 | 822 |
| 808 if current_list: | 823 if current_list: |
| 809 self._SaveLatestGroupSummary(current_list) | 824 self._SaveGroupSummary(current_list) |
| 810 | 825 |
| 811 if errors: | 826 if errors: |
| 812 logging.error("%d total new errors found" % errors) | 827 logging.error("%d total new errors found" % errors) |
| 813 return -1 | 828 return -1 |
| 814 else: | 829 else: |
| 815 logging.info("no new errors found - yay!") | 830 logging.info("no new errors found - yay!") |
| 816 if fixes: | 831 if fixes: |
| 817 logging.warning("%d total errors unexpectedly fixed" % fixes) | 832 logging.warning("%d total errors unexpectedly fixed" % fixes) |
| 818 # magic return code to turn the builder orange (via ReturnCodeCommand) | 833 # magic return code to turn the builder orange (via ReturnCodeCommand) |
| 819 return -88 | 834 return -88 |
| (...skipping 25 matching lines...) Expand all Loading... |
| 845 parser.add_option("-n", "--name", | 860 parser.add_option("-n", "--name", |
| 846 help="name of the test being run " | 861 help="name of the test being run " |
| 847 "(used for output filenames)") | 862 "(used for output filenames)") |
| 848 parser.add_option("", "--data_dir", | 863 parser.add_option("", "--data_dir", |
| 849 help="path to where purify data files live") | 864 help="path to where purify data files live") |
| 850 parser.add_option("", "--bug_report", default=False, | 865 parser.add_option("", "--bug_report", default=False, |
| 851 action="store_true", | 866 action="store_true", |
| 852 help="print output as an attempted summary of bugs") | 867 help="print output as an attempted summary of bugs") |
| 853 parser.add_option("-v", "--verbose", action="store_true", default=False, | 868 parser.add_option("-v", "--verbose", action="store_true", default=False, |
| 854 help="verbose output - enable debug log messages") | 869 help="verbose output - enable debug log messages") |
| 870 parser.add_option("", "--report_dir", |
| 871 help="path where report files are saved") |
| 855 | 872 |
| 856 (options, args) = parser.parse_args() | 873 (options, args) = parser.parse_args() |
| 857 if not len(args) >= 1: | 874 if not len(args) >= 1: |
| 858 parser.error("no filename specified") | 875 parser.error("no filename specified") |
| 859 filenames = args | 876 filenames = args |
| 860 | 877 |
| 861 if options.verbose: | 878 if options.verbose: |
| 862 google.logging_utils.config_root(level=logging.DEBUG) | 879 google.logging_utils.config_root(level=logging.DEBUG) |
| 863 else: | 880 else: |
| 864 google.logging_utils.config_root(level=logging.INFO) | 881 google.logging_utils.config_root(level=logging.INFO) |
| 865 pa = PurifyAnalyze(filenames, options.echo_to_stdout, options.name, | 882 pa = PurifyAnalyze(filenames, options.echo_to_stdout, options.name, |
| 866 options.source_dir, options.data_dir) | 883 options.source_dir, options.data_dir, options.report_dir) |
| 867 execute_crash = not pa.ReadFile() | 884 execute_crash = not pa.ReadFile() |
| 868 if options.bug_report: | 885 if options.bug_report: |
| 869 pa.PrintBugReport() | 886 pa.PrintBugReport() |
| 870 pa.PrintSummary(False) | 887 pa.PrintSummary(False) |
| 871 elif options.memory_in_use: | 888 elif options.memory_in_use: |
| 872 pa.PrintMemoryInUse(int(options.byte_filter)) | 889 pa.PrintMemoryInUse(int(options.byte_filter)) |
| 873 elif execute_crash: | 890 elif execute_crash: |
| 874 retcode = -1 | 891 retcode = -1 |
| 875 logging.error("Fatal error during test execution. Analysis skipped.") | 892 logging.error("Fatal error during test execution. Analysis skipped.") |
| 876 elif options.validate: | 893 elif options.validate: |
| 877 if pa.CompareResults() != 0: | 894 if pa.CompareResults() != 0: |
| 878 retcode = -1 | 895 retcode = -1 |
| 879 script_dir = google.path_utils.ScriptDir() | 896 pa.SaveResults() |
| 880 latest_dir = os.path.join(script_dir, "latest") | |
| 881 pa.SaveResults(latest_dir) | |
| 882 pa.PrintSummary() | 897 pa.PrintSummary() |
| 883 elif options.baseline: | 898 elif options.baseline: |
| 884 if not pa.SaveResults(verbose=True): | 899 if not pa.SaveResults(verbose=True): |
| 885 retcode = -1 | 900 retcode = -1 |
| 886 pa.PrintSummary(False) | 901 pa.PrintSummary(False) |
| 887 else: | 902 else: |
| 888 pa.PrintSummary(False) | 903 pa.PrintSummary(False) |
| 889 | 904 |
| 890 sys.exit(retcode) | 905 sys.exit(retcode) |
| 891 | 906 |
| 892 if __name__ == "__main__": | 907 if __name__ == "__main__": |
| 893 _main() | 908 _main() |
| 894 | 909 |
| 895 | 910 |
| OLD | NEW |