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)
|
|