[Mac] PepperFlash default on canary, opt-in on dev.
[chromium-blink-merge.git] / tools / heapcheck / heapcheck_test.py
blob1b7e7ec0fecf0c100e84c233798d691b8365de7f
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Wrapper for running the test under heapchecker and analyzing the output."""
7 import datetime
8 import logging
9 import os
10 import re
12 import common
13 import path_utils
14 import suppressions
17 class HeapcheckWrapper(object):
18 TMP_FILE = 'heapcheck.log'
19 SANITY_TEST_SUPPRESSION = "Heapcheck sanity test"
20 LEAK_REPORT_RE = re.compile(
21 'Leak of ([0-9]*) bytes in ([0-9]*) objects allocated from:')
22 STACK_LINE_RE = re.compile('\s*@\s*(?:0x)?[0-9a-fA-F]+\s*([^\n]*)')
23 BORING_CALLERS = common.BoringCallers(mangled=False, use_re_wildcards=True)
25 def __init__(self, supp_files):
26 self._mode = 'strict'
27 self._timeout = 1200
28 self._nocleanup_on_exit = False
29 self._suppressions = []
30 for fname in supp_files:
31 self._suppressions.extend(suppressions.ReadSuppressionsFromFile(fname))
32 if os.path.exists(self.TMP_FILE):
33 os.remove(self.TMP_FILE)
35 def PutEnvAndLog(self, env_name, env_value):
36 """Sets the env var |env_name| to |env_value| and writes to logging.info.
37 """
38 os.putenv(env_name, env_value)
39 logging.info('export %s=%s', env_name, env_value)
41 def Execute(self):
42 """Executes the app to be tested."""
43 logging.info('starting execution...')
44 proc = ['sh', path_utils.ScriptDir() + '/heapcheck_std.sh']
45 proc += self._args
46 self.PutEnvAndLog('G_SLICE', 'always-malloc')
47 self.PutEnvAndLog('NSS_DISABLE_ARENA_FREE_LIST', '1')
48 self.PutEnvAndLog('NSS_DISABLE_UNLOAD', '1')
49 self.PutEnvAndLog('GTEST_DEATH_TEST_USE_FORK', '1')
50 self.PutEnvAndLog('HEAPCHECK', self._mode)
51 self.PutEnvAndLog('HEAP_CHECK_ERROR_EXIT_CODE', '0')
52 self.PutEnvAndLog('HEAP_CHECK_MAX_LEAKS', '-1')
53 self.PutEnvAndLog('KEEP_SHADOW_STACKS', '1')
54 self.PutEnvAndLog('PPROF_PATH',
55 path_utils.ScriptDir() +
56 '/../../third_party/tcmalloc/chromium/src/pprof')
57 self.PutEnvAndLog('LD_LIBRARY_PATH',
58 '/usr/lib/debug/:/usr/lib32/debug/')
60 return common.RunSubprocess(proc, self._timeout)
62 def Analyze(self, log_lines, check_sanity=False):
63 """Analyzes the app's output and applies suppressions to the reports.
65 Analyze() searches the logs for leak reports and tries to apply
66 suppressions to them. Unsuppressed reports and other log messages are
67 dumped as is.
69 If |check_sanity| is True, the list of suppressed reports is searched for a
70 report starting with SANITY_TEST_SUPPRESSION. If there isn't one, Analyze
71 returns 2 regardless of the unsuppressed reports.
73 Args:
74 log_lines: An iterator over the app's log lines.
75 check_sanity: A flag that determines whether we should check the tool's
76 sanity.
77 Returns:
78 2, if the sanity check fails,
79 1, if unsuppressed reports remain in the output and the sanity check
80 passes,
81 0, if all the errors are suppressed and the sanity check passes.
82 """
83 return_code = 0
84 # leak signature: [number of bytes, number of objects]
85 cur_leak_signature = None
86 cur_stack = []
87 cur_report = []
88 reported_hashes = {}
89 # Statistics grouped by suppression description:
90 # [hit count, bytes, objects].
91 used_suppressions = {}
92 for line in log_lines:
93 line = line.rstrip() # remove the trailing \n
94 match = self.STACK_LINE_RE.match(line)
95 if match:
96 cur_stack.append(match.groups()[0])
97 cur_report.append(line)
98 continue
99 else:
100 if cur_stack:
101 # Try to find the suppression that applies to the current leak stack.
102 description = ''
103 for supp in self._suppressions:
104 if supp.Match(cur_stack):
105 cur_stack = []
106 description = supp.description
107 break
108 if cur_stack:
109 if not cur_leak_signature:
110 print 'Missing leak signature for the following stack: '
111 for frame in cur_stack:
112 print ' ' + frame
113 print 'Aborting...'
114 return 3
116 # Drop boring callers from the stack to get less redundant info
117 # and fewer unique reports.
118 found_boring = False
119 for i in range(1, len(cur_stack)):
120 for j in self.BORING_CALLERS:
121 if re.match(j, cur_stack[i]):
122 cur_stack = cur_stack[:i]
123 cur_report = cur_report[:i]
124 found_boring = True
125 break
126 if found_boring:
127 break
129 error_hash = hash("".join(cur_stack)) & 0xffffffffffffffff
130 if error_hash not in reported_hashes:
131 reported_hashes[error_hash] = 1
132 # Print the report and set the return code to 1.
133 print ('Leak of %d bytes in %d objects allocated from:'
134 % tuple(cur_leak_signature))
135 print '\n'.join(cur_report)
136 return_code = 1
137 # Generate the suppression iff the stack contains more than one
138 # frame (otherwise it's likely to be broken)
139 if len(cur_stack) > 1 or found_boring:
140 print '\nSuppression (error hash=#%016X#):\n{' % (error_hash)
141 print ' <insert_a_suppression_name_here>'
142 print ' Heapcheck:Leak'
143 for frame in cur_stack:
144 print ' fun:' + frame
145 print '}\n\n'
146 else:
147 print ('This stack may be broken due to omitted frame pointers.'
148 ' It is not recommended to suppress it.\n')
149 else:
150 # Update the suppressions histogram.
151 if description in used_suppressions:
152 hits, bytes, objects = used_suppressions[description]
153 hits += 1
154 bytes += cur_leak_signature[0]
155 objects += cur_leak_signature[1]
156 used_suppressions[description] = [hits, bytes, objects]
157 else:
158 used_suppressions[description] = [1] + cur_leak_signature
159 cur_stack = []
160 cur_report = []
161 cur_leak_signature = None
162 match = self.LEAK_REPORT_RE.match(line)
163 if match:
164 cur_leak_signature = map(int, match.groups())
165 else:
166 print line
167 # Print the list of suppressions used.
168 is_sane = False
169 if used_suppressions:
170 print
171 print '-----------------------------------------------------'
172 print 'Suppressions used:'
173 print ' count bytes objects name'
174 histo = {}
175 for description in used_suppressions:
176 if description.startswith(HeapcheckWrapper.SANITY_TEST_SUPPRESSION):
177 is_sane = True
178 hits, bytes, objects = used_suppressions[description]
179 line = '%8d %8d %8d %s' % (hits, bytes, objects, description)
180 if hits in histo:
181 histo[hits].append(line)
182 else:
183 histo[hits] = [line]
184 keys = histo.keys()
185 keys.sort()
186 for count in keys:
187 for line in histo[count]:
188 print line
189 print '-----------------------------------------------------'
190 if check_sanity and not is_sane:
191 logging.error("Sanity check failed")
192 return 2
193 else:
194 return return_code
196 def RunTestsAndAnalyze(self, check_sanity):
197 exec_retcode = self.Execute()
198 log_file = file(self.TMP_FILE, 'r')
199 analyze_retcode = self.Analyze(log_file, check_sanity)
200 log_file.close()
202 if analyze_retcode:
203 logging.error("Analyze failed.")
204 return analyze_retcode
206 if exec_retcode:
207 logging.error("Test execution failed.")
208 return exec_retcode
209 else:
210 logging.info("Test execution completed successfully.")
212 return 0
214 def Main(self, args, check_sanity=False):
215 self._args = args
216 start = datetime.datetime.now()
217 retcode = -1
218 retcode = self.RunTestsAndAnalyze(check_sanity)
219 end = datetime.datetime.now()
220 seconds = (end - start).seconds
221 hours = seconds / 3600
222 seconds %= 3600
223 minutes = seconds / 60
224 seconds %= 60
225 logging.info('elapsed time: %02d:%02d:%02d', hours, minutes, seconds)
226 logging.info('For more information on the Heapcheck bot see '
227 'http://dev.chromium.org/developers/how-tos/'
228 'using-the-heap-leak-checker')
229 return retcode
232 def RunTool(args, supp_files, module):
233 tool = HeapcheckWrapper(supp_files)
234 MODULES_TO_SANITY_CHECK = ["base"]
235 check_sanity = module in MODULES_TO_SANITY_CHECK
236 return tool.Main(args[1:], check_sanity)