2 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
8 ''' Given a Dr. Memory output file, parses errors and uniques them.'''
10 from collections import defaultdict
22 def __init__(self, report, suppression, testcase):
24 self._testcase = testcase
26 # Chromium-specific transformations of the suppressions:
27 # Replace 'any_test.exe' and 'chrome.dll' with '*', then remove the
28 # Dr.Memory-generated error ids from the name= lines as they don't
29 # make sense in a multiprocess report.
30 supp_lines = suppression.split("\n")
31 for l in xrange(len(supp_lines)):
32 if supp_lines[l].startswith("name="):
33 supp_lines[l] = "name=<insert_a_suppression_name_here>"
34 if supp_lines[l].startswith("chrome.dll!"):
35 supp_lines[l] = supp_lines[l].replace("chrome.dll!", "*!")
36 bang_index = supp_lines[l].find("!")
37 d_exe_index = supp_lines[l].find(".exe!")
38 if bang_index >= 4 and d_exe_index + 4 == bang_index:
39 supp_lines[l] = "*" + supp_lines[l][bang_index:]
40 self._suppression = "\n".join(supp_lines)
43 output = self._report + "\n"
45 output += "The report came from the `%s` test.\n" % self._testcase
46 output += "Suppression (error hash=#%016X#):\n" % self.ErrorHash()
47 output += (" For more info on using suppressions see "
48 "http://dev.chromium.org/developers/how-tos/using-drmemory#TOC-Suppressing-error-reports-from-the-\n")
49 output += "{\n%s\n}\n" % self._suppression
52 # This is a device-independent hash identifying the suppression.
53 # By printing out this hash we can find duplicate reports between tests and
54 # different shards running on multiple buildbots
56 return int(hashlib.md5(self._suppression).hexdigest()[:16], 16)
59 return hash(self._suppression)
61 def __eq__(self, rhs):
62 return self._suppression == rhs
65 class DrMemoryAnalyzer:
66 ''' Given a set of Dr.Memory output files, parse all the errors out of
67 them, unique them and output the results.'''
70 self.known_errors = set()
74 self.line_ = self.cur_fd_.readline()
76 def ReadSection(self):
79 while len(self.line_.strip()) > 0:
80 result.append(self.line_)
84 def ParseReportFile(self, filename, testcase):
87 # First, read the generated suppressions file so we can easily lookup a
88 # suppression for a given error.
89 supp_fd = open(filename.replace("results", "suppress"), 'r')
90 generated_suppressions = {} # Key -> Error #, Value -> Suppression text.
92 # NOTE: this regexp looks fragile. Might break if the generated
93 # suppression format slightly changes.
94 m = re.search("# Suppression for Error #([0-9]+)", line.strip())
97 error_id = int(m.groups()[0])
98 assert error_id not in generated_suppressions
99 # OK, now read the next suppression:
101 for supp_line in supp_fd:
102 if supp_line.startswith("#") or supp_line.strip() == "":
104 cur_supp += supp_line
105 generated_suppressions[error_id] = cur_supp.strip()
108 self.cur_fd_ = open(filename, 'r')
111 if (self.line_ == ''): break
113 match = re.search("^Error #([0-9]+): (.*)", self.line_)
115 error_id = int(match.groups()[0])
116 self.line_ = match.groups()[1].strip() + "\n"
117 report = "".join(self.ReadSection()).strip()
118 suppression = generated_suppressions[error_id]
119 ret.append(DrMemoryError(report, suppression, testcase))
121 if re.search("SUPPRESSIONS USED:", self.line_):
123 while self.line_.strip() != "":
124 line = self.line_.strip()
125 (count, name) = re.match(" *([0-9\?]+)x(?: \(.*?\))?: (.*)",
128 # Whole-module have no count available: assume 1
132 self.used_suppressions[name] += count
135 if self.line_.startswith("ASSERT FAILURE"):
136 ret.append(self.line_.strip())
141 def Report(self, filenames, testcase, check_sanity):
143 # TODO(timurrrr): support positive tests / check_sanity==True
144 self.used_suppressions = defaultdict(int)
147 reports_for_this_test = set()
149 cur_reports = self.ParseReportFile(f, testcase)
151 # Filter out the reports that were there in previous tests.
152 for r in cur_reports:
153 if r in reports_for_this_test:
154 # A similar report is about to be printed for this test.
156 elif r in self.known_errors:
157 # A similar report has already been printed in one of the prev tests.
158 to_report.append("This error was already printed in some "
159 "other test, see 'hash=#%016X#'" % r.ErrorHash())
160 reports_for_this_test.add(r)
162 self.known_errors.add(r)
163 reports_for_this_test.add(r)
166 common.PrintUsedSuppressionsList(self.used_suppressions)
169 logging.info("PASS: No error reports found")
174 logging.info("Found %i error reports" % len(to_report))
175 for report in to_report:
176 self.error_count += 1
177 logging.info("Report #%d\n%s" % (self.error_count, report))
178 logging.info("Total: %i error reports" % len(to_report))
184 '''For testing only. The DrMemoryAnalyze class should be imported instead.'''
185 parser = optparse.OptionParser("usage: %prog <files to analyze>")
187 (options, args) = parser.parse_args()
189 parser.error("no filename specified")
192 logging.getLogger().setLevel(logging.INFO)
193 return DrMemoryAnalyzer().Report(filenames, None, False)
196 if __name__ == '__main__':