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
)
44 output
+= "### BEGIN MEMORY TOOL REPORT (error hash=#%016X#)\n" % \
46 output
+= self
._report
+ "\n"
48 output
+= "The report came from the `%s` test.\n" % self
._testcase
49 output
+= "Suppression (error hash=#%016X#):\n" % self
.ErrorHash()
50 output
+= (" For more info on using suppressions see "
51 "http://dev.chromium.org/developers/how-tos/using-drmemory#TOC-Suppressing-error-reports-from-the-\n")
52 output
+= "{\n%s\n}\n" % self
._suppression
53 output
+= "### END MEMORY TOOL REPORT (error hash=#%016X#)\n" % \
57 # This is a device-independent hash identifying the suppression.
58 # By printing out this hash we can find duplicate reports between tests and
59 # different shards running on multiple buildbots
61 return int(hashlib
.md5(self
._suppression
).hexdigest()[:16], 16)
64 return hash(self
._suppression
)
66 def __eq__(self
, rhs
):
67 return self
._suppression
== rhs
70 class DrMemoryAnalyzer
:
71 ''' Given a set of Dr.Memory output files, parse all the errors out of
72 them, unique them and output the results.'''
75 self
.known_errors
= set()
79 self
.line_
= self
.cur_fd_
.readline()
81 def ReadSection(self
):
84 while len(self
.line_
.strip()) > 0:
85 result
.append(self
.line_
)
89 def ParseReportFile(self
, filename
, testcase
):
92 # First, read the generated suppressions file so we can easily lookup a
93 # suppression for a given error.
94 supp_fd
= open(filename
.replace("results", "suppress"), 'r')
95 generated_suppressions
= {} # Key -> Error #, Value -> Suppression text.
97 # NOTE: this regexp looks fragile. Might break if the generated
98 # suppression format slightly changes.
99 m
= re
.search("# Suppression for Error #([0-9]+)", line
.strip())
102 error_id
= int(m
.groups()[0])
103 assert error_id
not in generated_suppressions
104 # OK, now read the next suppression:
106 for supp_line
in supp_fd
:
107 if supp_line
.startswith("#") or supp_line
.strip() == "":
109 cur_supp
+= supp_line
110 generated_suppressions
[error_id
] = cur_supp
.strip()
113 self
.cur_fd_
= open(filename
, 'r')
116 if (self
.line_
== ''): break
118 match
= re
.search("^Error #([0-9]+): (.*)", self
.line_
)
120 error_id
= int(match
.groups()[0])
121 self
.line_
= match
.groups()[1].strip() + "\n"
122 report
= "".join(self
.ReadSection()).strip()
123 suppression
= generated_suppressions
[error_id
]
124 ret
.append(DrMemoryError(report
, suppression
, testcase
))
126 if re
.search("SUPPRESSIONS USED:", self
.line_
):
128 while self
.line_
.strip() != "":
129 line
= self
.line_
.strip()
130 (count
, name
) = re
.match(" *([0-9\?]+)x(?: \(.*?\))?: (.*)",
133 # Whole-module have no count available: assume 1
137 self
.used_suppressions
[name
] += count
140 if self
.line_
.startswith("ASSERT FAILURE"):
141 ret
.append(self
.line_
.strip())
146 def Report(self
, filenames
, testcase
, check_sanity
):
148 # TODO(timurrrr): support positive tests / check_sanity==True
149 self
.used_suppressions
= defaultdict(int)
152 reports_for_this_test
= set()
154 cur_reports
= self
.ParseReportFile(f
, testcase
)
156 # Filter out the reports that were there in previous tests.
157 for r
in cur_reports
:
158 if r
in reports_for_this_test
:
159 # A similar report is about to be printed for this test.
161 elif r
in self
.known_errors
:
162 # A similar report has already been printed in one of the prev tests.
163 to_report
.append("This error was already printed in some "
164 "other test, see 'hash=#%016X#'" % r
.ErrorHash())
165 reports_for_this_test
.add(r
)
167 self
.known_errors
.add(r
)
168 reports_for_this_test
.add(r
)
171 common
.PrintUsedSuppressionsList(self
.used_suppressions
)
174 logging
.info("PASS: No error reports found")
179 logging
.info("Found %i error reports" % len(to_report
))
180 for report
in to_report
:
181 self
.error_count
+= 1
182 logging
.info("Report #%d\n%s" % (self
.error_count
, report
))
183 logging
.info("Total: %i error reports" % len(to_report
))
189 '''For testing only. The DrMemoryAnalyze class should be imported instead.'''
190 parser
= optparse
.OptionParser("usage: %prog <files to analyze>")
192 (options
, args
) = parser
.parse_args()
194 parser
.error("no filename specified")
197 logging
.getLogger().setLevel(logging
.INFO
)
198 return DrMemoryAnalyzer().Report(filenames
, None, False)
201 if __name__
== '__main__':