summaryrefslogtreecommitdiffstats
path: root/tools/heapcheck/heapcheck_test.py
diff options
context:
space:
mode:
authortimurrrr@chromium.org <timurrrr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-12-01 17:56:01 +0000
committertimurrrr@chromium.org <timurrrr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-12-01 17:56:01 +0000
commit741e1971f600c76a8ac47a548352578da981cbe0 (patch)
tree25a03e8c307b7b280f994dda4641f10d25b2b0d6 /tools/heapcheck/heapcheck_test.py
parentf2c20fa96403a8db390bc7790b67e1906dad179b (diff)
downloadchromium_src-741e1971f600c76a8ac47a548352578da981cbe0.zip
chromium_src-741e1971f600c76a8ac47a548352578da981cbe0.tar.gz
chromium_src-741e1971f600c76a8ac47a548352578da981cbe0.tar.bz2
Created tools/heapcheck with scripts for running heapchecker-enabled tests.
chrome_tests.sh -- a frontend for the buildbot slave to run the tests under heap leak checker. chrome_tests.py -- almost copied from the similar scripts in ../valgrind/ and ../purify/, invokes heapcheck_test.py for each target. heapcheck_test.py -- runs a single test, processes the logs and applies suppressions to the reports returned by the heap checker. heapcheck_std.sh -- a tiny wrapper for IO redirection. suppressions.py -- provides the support for Valgrind-style stack trace suppressions. This patch was originally prepared by Alexander Potapenko (cc'ed) as http://codereview.chromium.org/400010 TBR=dank,erikkay Review URL: http://codereview.chromium.org/455021 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@33451 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/heapcheck/heapcheck_test.py')
-rw-r--r--tools/heapcheck/heapcheck_test.py166
1 files changed, 166 insertions, 0 deletions
diff --git a/tools/heapcheck/heapcheck_test.py b/tools/heapcheck/heapcheck_test.py
new file mode 100644
index 0000000..50ccfc5
--- /dev/null
+++ b/tools/heapcheck/heapcheck_test.py
@@ -0,0 +1,166 @@
+#!/usr/bin/python
+# Copyright (c) 2006-2009 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.
+
+# heapcheck_test.py
+
+"""Wrapper for running the test under heapchecker and analyzing the output."""
+
+import datetime
+import logging
+import os
+import re
+
+import common
+import google.path_utils
+import suppressions
+
+
+class HeapcheckWrapper(object):
+ TMP_FILE = 'heapcheck.log'
+
+ def __init__(self, supp_files):
+ self._mode = 'strict'
+ self._timeout = 1200
+ 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', google.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('GTEST_DEATH_TEST_USE_FORK', '1')
+ self.PutEnvAndLog('HEAPCHECK', self._mode)
+
+ common.RunSubprocess(proc, self._timeout)
+
+ # Always return true, even if running the subprocess failed. We depend on
+ # Analyze to determine if the run was valid. (This behaviour copied from
+ # the purify_test.py script.)
+ return True
+
+ def Analyze(self, log_lines):
+ """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.
+
+ Args:
+ log_lines: An iterator over the app's log lines.
+ Returns:
+ 1, if unsuppressed reports remain in the output, 0 otherwise.
+ """
+ leak_report = re.compile(
+ 'Leak of ([0-9]*) bytes in ([0-9]*) objects allocated from:')
+ stack_line = re.compile('\s*@\s*0x[0-9a-fA-F]*\s*(\S*)')
+ return_code = 0
+ # leak signature: [number of bytes, number of objects]
+ cur_leak_signature = None
+ cur_stack = []
+ cur_report = []
+ # Statistics grouped by suppression description:
+ # [hit count, bytes, objects].
+ used_suppressions = {}
+ for line in log_lines:
+ line = line.rstrip() # remove the trailing \n
+ match = stack_line.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:
+ # 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
+ 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 = leak_report.match(line)
+ if match:
+ cur_leak_signature = map(int, match.groups())
+ else:
+ print line
+ # Print the list of suppressions used.
+ if used_suppressions:
+ print
+ print '-----------------------------------------------------'
+ print 'Suppressions used:'
+ print ' count bytes objects name'
+ histo = {}
+ for description in used_suppressions:
+ 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 '-----------------------------------------------------'
+
+ return return_code
+
+ def RunTestsAndAnalyze(self):
+ self.Execute()
+ log_file = file(self.TMP_FILE, 'r')
+ ret = self.Analyze(log_file)
+ log_file.close()
+ return ret
+
+ def Main(self, args):
+ self._args = args
+ start = datetime.datetime.now()
+ retcode = -1
+ retcode = self.RunTestsAndAnalyze()
+ 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)
+ return retcode
+
+
+def RunTool(args, supp_files):
+ tool = HeapcheckWrapper(supp_files)
+ return tool.Main(args[1:])