| Index: tools/heapcheck/heapcheck_test.py
|
| diff --git a/tools/heapcheck/heapcheck_test.py b/tools/heapcheck/heapcheck_test.py
|
| deleted file mode 100644
|
| index 6987624146c981e235f70a94da5c6e7af0958e54..0000000000000000000000000000000000000000
|
| --- a/tools/heapcheck/heapcheck_test.py
|
| +++ /dev/null
|
| @@ -1,250 +0,0 @@
|
| -# Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| -# Use of this source code is governed by a BSD-style license that can be
|
| -# found in the LICENSE file.
|
| -
|
| -"""Wrapper for running the test under heapchecker and analyzing the output."""
|
| -
|
| -import datetime
|
| -import logging
|
| -import os
|
| -import re
|
| -
|
| -import common
|
| -import path_utils
|
| -import suppressions
|
| -
|
| -
|
| -class HeapcheckWrapper(object):
|
| - TMP_FILE = 'heapcheck.log'
|
| - SANITY_TEST_SUPPRESSION = "Heapcheck sanity test"
|
| - LEAK_REPORT_RE = re.compile(
|
| - 'Leak of ([0-9]*) bytes in ([0-9]*) objects allocated from:')
|
| - # Workaround for http://crbug.com/132867, see below.
|
| - HOOKED_ALLOCATOR_RE = re.compile(
|
| - 'Hooked allocator frame not found, returning empty trace')
|
| - STACK_LINE_RE = re.compile('\s*@\s*(?:0x)?[0-9a-fA-F]+\s*([^\n]*)')
|
| - BORING_CALLERS = common.BoringCallers(mangled=False, use_re_wildcards=True)
|
| -
|
| - def __init__(self, supp_files):
|
| - self._mode = 'strict'
|
| - self._timeout = 3600
|
| - self._nocleanup_on_exit = False
|
| - self._suppressions = []
|
| - for fname in supp_files:
|
| - self._suppressions.extend(suppressions.ReadSuppressionsFromFile(fname))
|
| - if os.path.exists(self.TMP_FILE):
|
| - os.remove(self.TMP_FILE)
|
| -
|
| - def PutEnvAndLog(self, env_name, env_value):
|
| - """Sets the env var |env_name| to |env_value| and writes to logging.info.
|
| - """
|
| - os.putenv(env_name, env_value)
|
| - logging.info('export %s=%s', env_name, env_value)
|
| -
|
| - def Execute(self):
|
| - """Executes the app to be tested."""
|
| - logging.info('starting execution...')
|
| - proc = ['sh', path_utils.ScriptDir() + '/heapcheck_std.sh']
|
| - proc += self._args
|
| - self.PutEnvAndLog('G_SLICE', 'always-malloc')
|
| - self.PutEnvAndLog('NSS_DISABLE_ARENA_FREE_LIST', '1')
|
| - self.PutEnvAndLog('NSS_DISABLE_UNLOAD', '1')
|
| - self.PutEnvAndLog('GTEST_DEATH_TEST_USE_FORK', '1')
|
| - self.PutEnvAndLog('HEAPCHECK', self._mode)
|
| - self.PutEnvAndLog('HEAP_CHECK_ERROR_EXIT_CODE', '0')
|
| - self.PutEnvAndLog('HEAP_CHECK_MAX_LEAKS', '-1')
|
| - self.PutEnvAndLog('KEEP_SHADOW_STACKS', '1')
|
| - self.PutEnvAndLog('PPROF_PATH',
|
| - path_utils.ScriptDir() +
|
| - '/../../third_party/tcmalloc/chromium/src/pprof')
|
| - self.PutEnvAndLog('LD_LIBRARY_PATH',
|
| - '/usr/lib/debug/:/usr/lib32/debug/')
|
| - # CHROME_DEVEL_SANDBOX causes problems with heapcheck
|
| - self.PutEnvAndLog('CHROME_DEVEL_SANDBOX', '');
|
| -
|
| - return common.RunSubprocess(proc, self._timeout)
|
| -
|
| - def Analyze(self, log_lines, check_sanity=False):
|
| - """Analyzes the app's output and applies suppressions to the reports.
|
| -
|
| - Analyze() searches the logs for leak reports and tries to apply
|
| - suppressions to them. Unsuppressed reports and other log messages are
|
| - dumped as is.
|
| -
|
| - If |check_sanity| is True, the list of suppressed reports is searched for a
|
| - report starting with SANITY_TEST_SUPPRESSION. If there isn't one, Analyze
|
| - returns 2 regardless of the unsuppressed reports.
|
| -
|
| - Args:
|
| - log_lines: An iterator over the app's log lines.
|
| - check_sanity: A flag that determines whether we should check the tool's
|
| - sanity.
|
| - Returns:
|
| - 2, if the sanity check fails,
|
| - 1, if unsuppressed reports remain in the output and the sanity check
|
| - passes,
|
| - 0, if all the errors are suppressed and the sanity check passes.
|
| - """
|
| - return_code = 0
|
| - # leak signature: [number of bytes, number of objects]
|
| - cur_leak_signature = None
|
| - cur_stack = []
|
| - cur_report = []
|
| - reported_hashes = {}
|
| - # Statistics grouped by suppression description:
|
| - # [hit count, bytes, objects].
|
| - used_suppressions = {}
|
| - hooked_allocator_line_encountered = False
|
| - for line in log_lines:
|
| - line = line.rstrip() # remove the trailing \n
|
| - match = self.STACK_LINE_RE.match(line)
|
| - if match:
|
| - cur_stack.append(match.groups()[0])
|
| - cur_report.append(line)
|
| - continue
|
| - else:
|
| - if cur_stack:
|
| - # Try to find the suppression that applies to the current leak stack.
|
| - description = ''
|
| - for supp in self._suppressions:
|
| - if supp.Match(cur_stack):
|
| - cur_stack = []
|
| - description = supp.description
|
| - break
|
| - if cur_stack:
|
| - if not cur_leak_signature:
|
| - print 'Missing leak signature for the following stack: '
|
| - for frame in cur_stack:
|
| - print ' ' + frame
|
| - print 'Aborting...'
|
| - return 3
|
| -
|
| - # Drop boring callers from the stack to get less redundant info
|
| - # and fewer unique reports.
|
| - found_boring = False
|
| - for i in range(1, len(cur_stack)):
|
| - for j in self.BORING_CALLERS:
|
| - if re.match(j, cur_stack[i]):
|
| - cur_stack = cur_stack[:i]
|
| - cur_report = cur_report[:i]
|
| - found_boring = True
|
| - break
|
| - if found_boring:
|
| - break
|
| -
|
| - error_hash = hash("".join(cur_stack)) & 0xffffffffffffffff
|
| - if error_hash not in reported_hashes:
|
| - reported_hashes[error_hash] = 1
|
| - # Print the report and set the return code to 1.
|
| - print ('Leak of %d bytes in %d objects allocated from:'
|
| - % tuple(cur_leak_signature))
|
| - print '\n'.join(cur_report)
|
| - return_code = 1
|
| - # Generate the suppression iff the stack contains more than one
|
| - # frame (otherwise it's likely to be broken)
|
| - if len(cur_stack) > 1 or found_boring:
|
| - print '\nSuppression (error hash=#%016X#):\n{' % (error_hash)
|
| - print ' <insert_a_suppression_name_here>'
|
| - print ' Heapcheck:Leak'
|
| - for frame in cur_stack:
|
| - print ' fun:' + frame
|
| - print '}\n\n'
|
| - else:
|
| - print ('This stack may be broken due to omitted frame pointers.'
|
| - ' It is not recommended to suppress it.\n')
|
| - else:
|
| - # Update the suppressions histogram.
|
| - if description in used_suppressions:
|
| - hits, bytes, objects = used_suppressions[description]
|
| - hits += 1
|
| - bytes += cur_leak_signature[0]
|
| - objects += cur_leak_signature[1]
|
| - used_suppressions[description] = [hits, bytes, objects]
|
| - else:
|
| - used_suppressions[description] = [1] + cur_leak_signature
|
| - cur_stack = []
|
| - cur_report = []
|
| - cur_leak_signature = None
|
| - match = self.LEAK_REPORT_RE.match(line)
|
| - if match:
|
| - cur_leak_signature = map(int, match.groups())
|
| - else:
|
| - match = self.HOOKED_ALLOCATOR_RE.match(line)
|
| - if match:
|
| - hooked_allocator_line_encountered = True
|
| - else:
|
| - print line
|
| - # Print the list of suppressions used.
|
| - is_sane = False
|
| - if used_suppressions:
|
| - print
|
| - print '-----------------------------------------------------'
|
| - print 'Suppressions used:'
|
| - print ' count bytes objects name'
|
| - histo = {}
|
| - for description in used_suppressions:
|
| - if description.startswith(HeapcheckWrapper.SANITY_TEST_SUPPRESSION):
|
| - is_sane = True
|
| - hits, bytes, objects = used_suppressions[description]
|
| - line = '%8d %8d %8d %s' % (hits, bytes, objects, description)
|
| - if hits in histo:
|
| - histo[hits].append(line)
|
| - else:
|
| - histo[hits] = [line]
|
| - keys = histo.keys()
|
| - keys.sort()
|
| - for count in keys:
|
| - for line in histo[count]:
|
| - print line
|
| - print '-----------------------------------------------------'
|
| - if hooked_allocator_line_encountered:
|
| - print ('WARNING: Workaround for http://crbug.com/132867 (tons of '
|
| - '"Hooked allocator frame not found, returning empty trace") '
|
| - 'in effect.')
|
| - if check_sanity and not is_sane:
|
| - logging.error("Sanity check failed")
|
| - return 2
|
| - else:
|
| - return return_code
|
| -
|
| - def RunTestsAndAnalyze(self, check_sanity):
|
| - exec_retcode = self.Execute()
|
| - log_file = file(self.TMP_FILE, 'r')
|
| - analyze_retcode = self.Analyze(log_file, check_sanity)
|
| - log_file.close()
|
| -
|
| - if analyze_retcode:
|
| - logging.error("Analyze failed.")
|
| - return analyze_retcode
|
| -
|
| - if exec_retcode:
|
| - logging.error("Test execution failed.")
|
| - return exec_retcode
|
| - else:
|
| - logging.info("Test execution completed successfully.")
|
| -
|
| - return 0
|
| -
|
| - def Main(self, args, check_sanity=False):
|
| - self._args = args
|
| - start = datetime.datetime.now()
|
| - retcode = -1
|
| - retcode = self.RunTestsAndAnalyze(check_sanity)
|
| - end = datetime.datetime.now()
|
| - seconds = (end - start).seconds
|
| - hours = seconds / 3600
|
| - seconds %= 3600
|
| - minutes = seconds / 60
|
| - seconds %= 60
|
| - logging.info('elapsed time: %02d:%02d:%02d', hours, minutes, seconds)
|
| - logging.info('For more information on the Heapcheck bot see '
|
| - 'http://dev.chromium.org/developers/how-tos/'
|
| - 'using-the-heap-leak-checker')
|
| - return retcode
|
| -
|
| -
|
| -def RunTool(args, supp_files, module):
|
| - tool = HeapcheckWrapper(supp_files)
|
| - MODULES_TO_SANITY_CHECK = ["base"]
|
| - check_sanity = module in MODULES_TO_SANITY_CHECK
|
| - return tool.Main(args[1:], check_sanity)
|
|
|