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."""
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
):
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.
38 os
.putenv(env_name
, env_value
)
39 logging
.info('export %s=%s', env_name
, env_value
)
42 """Executes the app to be tested."""
43 logging
.info('starting execution...')
44 proc
= ['sh', path_utils
.ScriptDir() + '/heapcheck_std.sh']
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/')
59 # CHROME_DEVEL_SANDBOX causes problems with heapcheck
60 self
.PutEnvAndLog('CHROME_DEVEL_SANDBOX', '');
62 return common
.RunSubprocess(proc
, self
._timeout
)
64 def Analyze(self
, log_lines
, check_sanity
=False):
65 """Analyzes the app's output and applies suppressions to the reports.
67 Analyze() searches the logs for leak reports and tries to apply
68 suppressions to them. Unsuppressed reports and other log messages are
71 If |check_sanity| is True, the list of suppressed reports is searched for a
72 report starting with SANITY_TEST_SUPPRESSION. If there isn't one, Analyze
73 returns 2 regardless of the unsuppressed reports.
76 log_lines: An iterator over the app's log lines.
77 check_sanity: A flag that determines whether we should check the tool's
80 2, if the sanity check fails,
81 1, if unsuppressed reports remain in the output and the sanity check
83 0, if all the errors are suppressed and the sanity check passes.
86 # leak signature: [number of bytes, number of objects]
87 cur_leak_signature
= None
91 # Statistics grouped by suppression description:
92 # [hit count, bytes, objects].
93 used_suppressions
= {}
94 for line
in log_lines
:
95 line
= line
.rstrip() # remove the trailing \n
96 match
= self
.STACK_LINE_RE
.match(line
)
98 cur_stack
.append(match
.groups()[0])
99 cur_report
.append(line
)
103 # Try to find the suppression that applies to the current leak stack.
105 for supp
in self
._suppressions
:
106 if supp
.Match(cur_stack
):
108 description
= supp
.description
111 if not cur_leak_signature
:
112 print 'Missing leak signature for the following stack: '
113 for frame
in cur_stack
:
118 # Drop boring callers from the stack to get less redundant info
119 # and fewer unique reports.
121 for i
in range(1, len(cur_stack
)):
122 for j
in self
.BORING_CALLERS
:
123 if re
.match(j
, cur_stack
[i
]):
124 cur_stack
= cur_stack
[:i
]
125 cur_report
= cur_report
[:i
]
131 error_hash
= hash("".join(cur_stack
)) & 0xffffffffffffffff
132 if error_hash
not in reported_hashes
:
133 reported_hashes
[error_hash
] = 1
134 # Print the report and set the return code to 1.
135 print ('Leak of %d bytes in %d objects allocated from:'
136 % tuple(cur_leak_signature
))
137 print '\n'.join(cur_report
)
139 # Generate the suppression iff the stack contains more than one
140 # frame (otherwise it's likely to be broken)
141 if len(cur_stack
) > 1 or found_boring
:
142 print '\nSuppression (error hash=#%016X#):\n{' % (error_hash
)
143 print ' <insert_a_suppression_name_here>'
144 print ' Heapcheck:Leak'
145 for frame
in cur_stack
:
146 print ' fun:' + frame
149 print ('This stack may be broken due to omitted frame pointers.'
150 ' It is not recommended to suppress it.\n')
152 # Update the suppressions histogram.
153 if description
in used_suppressions
:
154 hits
, bytes
, objects
= used_suppressions
[description
]
156 bytes
+= cur_leak_signature
[0]
157 objects
+= cur_leak_signature
[1]
158 used_suppressions
[description
] = [hits
, bytes
, objects
]
160 used_suppressions
[description
] = [1] + cur_leak_signature
163 cur_leak_signature
= None
164 match
= self
.LEAK_REPORT_RE
.match(line
)
166 cur_leak_signature
= map(int, match
.groups())
169 # Print the list of suppressions used.
171 if used_suppressions
:
173 print '-----------------------------------------------------'
174 print 'Suppressions used:'
175 print ' count bytes objects name'
177 for description
in used_suppressions
:
178 if description
.startswith(HeapcheckWrapper
.SANITY_TEST_SUPPRESSION
):
180 hits
, bytes
, objects
= used_suppressions
[description
]
181 line
= '%8d %8d %8d %s' % (hits
, bytes
, objects
, description
)
183 histo
[hits
].append(line
)
189 for line
in histo
[count
]:
191 print '-----------------------------------------------------'
192 if check_sanity
and not is_sane
:
193 logging
.error("Sanity check failed")
198 def RunTestsAndAnalyze(self
, check_sanity
):
199 exec_retcode
= self
.Execute()
200 log_file
= file(self
.TMP_FILE
, 'r')
201 analyze_retcode
= self
.Analyze(log_file
, check_sanity
)
205 logging
.error("Analyze failed.")
206 return analyze_retcode
209 logging
.error("Test execution failed.")
212 logging
.info("Test execution completed successfully.")
216 def Main(self
, args
, check_sanity
=False):
218 start
= datetime
.datetime
.now()
220 retcode
= self
.RunTestsAndAnalyze(check_sanity
)
221 end
= datetime
.datetime
.now()
222 seconds
= (end
- start
).seconds
223 hours
= seconds
/ 3600
225 minutes
= seconds
/ 60
227 logging
.info('elapsed time: %02d:%02d:%02d', hours
, minutes
, seconds
)
228 logging
.info('For more information on the Heapcheck bot see '
229 'http://dev.chromium.org/developers/how-tos/'
230 'using-the-heap-leak-checker')
234 def RunTool(args
, supp_files
, module
):
235 tool
= HeapcheckWrapper(supp_files
)
236 MODULES_TO_SANITY_CHECK
= ["base"]
237 check_sanity
= module
in MODULES_TO_SANITY_CHECK
238 return tool
.Main(args
[1:], check_sanity
)