summaryrefslogtreecommitdiffstats
path: root/tools/valgrind/valgrind_test.py
diff options
context:
space:
mode:
authordkegel@google.com <dkegel@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-16 15:55:52 +0000
committerdkegel@google.com <dkegel@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-16 15:55:52 +0000
commit68e3b47230aa88a848ba6cb7daee852993f51a62 (patch)
tree15203472cb668e0a6277c99a1ed1c5b0d67fec2c /tools/valgrind/valgrind_test.py
parent9229b2de068f352b3d184996206870d1d4ffc2b5 (diff)
downloadchromium_src-68e3b47230aa88a848ba6cb7daee852993f51a62.zip
chromium_src-68e3b47230aa88a848ba6cb7daee852993f51a62.tar.gz
chromium_src-68e3b47230aa88a848ba6cb7daee852993f51a62.tar.bz2
Patch from timurrrr: Re-factor valgrind scripts to add tsan support
First reviewed at http://codereview.chromium.org/125272 BUG=none TEST=none Review URL: http://codereview.chromium.org/155528 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@20870 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/valgrind/valgrind_test.py')
-rwxr-xr-xtools/valgrind/valgrind_test.py512
1 files changed, 315 insertions, 197 deletions
diff --git a/tools/valgrind/valgrind_test.py b/tools/valgrind/valgrind_test.py
index 66332ea..c9e7e46 100755
--- a/tools/valgrind/valgrind_test.py
+++ b/tools/valgrind/valgrind_test.py
@@ -3,7 +3,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-# purify_test.py
+# valgrind_test.py
'''Runs an exe through Valgrind and puts the intermediate files in a
directory.
@@ -22,13 +22,12 @@ import tempfile
import common
-import valgrind_analyze
+import memcheck_analyze
+import tsan_analyze
import google.logging_utils
-rmtree = shutil.rmtree
-
-class Valgrind(object):
+class ValgrindTool(object):
"""Abstract class for running Valgrind.
@@ -44,6 +43,10 @@ class Valgrind(object):
shutil.rmtree(self.TMP_DIR)
os.mkdir(self.TMP_DIR)
+ def ToolName(self):
+ raise RuntimeError, "This method should be implemented " \
+ "in the tool-specific subclass"
+
def CreateOptionParser(self):
self._parser = optparse.OptionParser("usage: %prog [options] <program to "
"test>")
@@ -53,67 +56,187 @@ class Valgrind(object):
self._parser.add_option("", "--source_dir",
help="path to top of source tree for this build"
"(used to normalize source paths in baseline)")
- self._parser.add_option("", "--suppressions", default=[],
- action="append",
- help="path to a valgrind suppression file")
self._parser.add_option("", "--gtest_filter", default="",
help="which test case to run")
self._parser.add_option("", "--gtest_print_time", action="store_true",
default=False,
help="show how long each test takes")
- self._parser.add_option("", "--trace_children", action="store_true",
- default=False,
- help="also trace child processes")
self._parser.add_option("", "--indirect", action="store_true",
default=False,
- help="set BROWSER_WRAPPER rather than running valgrind directly")
- self._parser.add_option("", "--show_all_leaks", action="store_true",
- default=False,
- help="also show less blatant leaks")
- self._parser.add_option("", "--track_origins", action="store_true",
+ help="set BROWSER_WRAPPER rather than "
+ "running valgrind directly")
+ self._parser.add_option("-v", "--verbose", action="store_true",
default=False,
- help="Show whence uninit bytes came. 30% slower.")
- self._parser.add_option("", "--generate_dsym", action="store_true",
+ help="verbose output - enable debug log messages")
+ self._parser.add_option("", "--trace_children", action="store_true",
default=False,
- help="Generate .dSYM file on Mac if needed. Slow!")
- self._parser.add_option("", "--generate_suppressions", action="store_true",
+ help="also trace child processes")
+ self._parser.add_option("", "--gen_suppressions", action="store_true",
+ dest="generate_suppressions", default=False,
+ help="skip analysis and generate suppressions")
+ self._parser.add_option("", "--num-callers",
+ dest="num_callers", default=30,
+ help="number of callers to show in stack traces")
+ self._parser.add_option("", "--nocleanup_on_exit", action="store_true",
default=False,
- help="Skip analysis and generate suppressions")
- self._parser.add_option("", "--custom_valgrind_command",
- help="Use custom valgrind command and options")
- self._parser.add_option("-v", "--verbose", action="store_true", default=False,
- help="verbose output - enable debug log messages")
+ help="don't delete directory with logs on exit")
+ self.ExtendOptionParser(self._parser)
self._parser.description = __doc__
- def ParseArgv(self):
+ def ExtendOptionParser(self, parser):
+ if sys.platform == 'darwin':
+ parser.add_option("", "--generate_dsym", action="store_true",
+ default=False,
+ help="Generate .dSYM file on Mac if needed. Slow!")
+
+ def ParseArgv(self, args):
self.CreateOptionParser()
- self._options, self._args = self._parser.parse_args()
+
+ # self._tool_flags will store those tool flags which we don't parse
+ # manually in this script.
+ self._tool_flags = []
+ known_args = []
+
+ """ We assume that the first argument not starting with "-" is a program name
+ and all the following flags should be passed to the program.
+ TODO(timurrrr): customize optparse instead
+ """
+ while len(args) > 0 and args[0][:1] == "-":
+ arg = args[0]
+ if (arg == "--"):
+ break
+ if self._parser.has_option(arg.split("=")[0]):
+ known_args += [arg]
+ else:
+ self._tool_flags += [arg]
+ args = args[1:]
+
+ if len(args) > 0:
+ known_args += args
+
+ self._options, self._args = self._parser.parse_args(known_args)
+
self._timeout = int(self._options.timeout)
- self._suppressions = self._options.suppressions
+ self._num_callers = int(self._options.num_callers)
self._generate_suppressions = self._options.generate_suppressions
+ self._suppressions = self._options.suppressions
self._source_dir = self._options.source_dir
+ self._nocleanup_on_exit = self._options.nocleanup_on_exit
if self._options.gtest_filter != "":
self._args.append("--gtest_filter=%s" % self._options.gtest_filter)
if self._options.gtest_print_time:
self._args.append("--gtest_print_time");
- if self._options.verbose:
- google.logging_utils.config_root(logging.DEBUG)
- else:
- google.logging_utils.config_root()
return True
- def Setup(self):
- return self.ParseArgv()
+ def Setup(self, args):
+ return self.ParseArgv(args)
def PrepareForTest(self):
- """Perform necessary tasks prior to executing the test."""
- pass
+ """Runs dsymutil if needed.
+
+ Valgrind for Mac OS X requires that debugging information be in a .dSYM
+ bundle generated by dsymutil. It is not currently able to chase DWARF
+ data into .o files like gdb does, so executables without .dSYM bundles or
+ with the Chromium-specific "fake_dsym" bundles generated by
+ build/mac/strip_save_dsym won't give source file and line number
+ information in valgrind.
+
+ This function will run dsymutil if the .dSYM bundle is missing or if
+ it looks like a fake_dsym. A non-fake dsym that already exists is assumed
+ to be up-to-date.
+ """
+ if sys.platform != 'darwin':
+ return
+
+ test_command = self._args[0]
+ dsym_bundle = self._args[0] + '.dSYM'
+ dsym_file = os.path.join(dsym_bundle, 'Contents', 'Resources', 'DWARF',
+ os.path.basename(test_command))
+ dsym_info_plist = os.path.join(dsym_bundle, 'Contents', 'Info.plist')
+
+ needs_dsymutil = True
+ saved_test_command = None
+
+ if os.path.exists(dsym_file) and os.path.exists(dsym_info_plist):
+ # Look for the special fake_dsym tag in dsym_info_plist.
+ dsym_info_plist_contents = open(dsym_info_plist).read()
+
+ if not re.search('^\s*<key>fake_dsym</key>$', dsym_info_plist_contents,
+ re.MULTILINE):
+ # fake_dsym is not set, this is a real .dSYM bundle produced by
+ # dsymutil. dsymutil does not need to be run again.
+ needs_dsymutil = False
+ else:
+ # fake_dsym is set. dsym_file is a copy of the original test_command
+ # before it was stripped. Copy it back to test_command so that
+ # dsymutil has unstripped input to work with. Move the stripped
+ # test_command out of the way, it will be restored when this is
+ # done.
+ saved_test_command = test_command + '.stripped'
+ os.rename(test_command, saved_test_command)
+ shutil.copyfile(dsym_file, test_command)
+ shutil.copymode(saved_test_command, test_command)
+
+ if needs_dsymutil:
+ if self._options.generate_dsym:
+ # Remove the .dSYM bundle if it exists.
+ shutil.rmtree(dsym_bundle, True)
+
+ dsymutil_command = ['dsymutil', test_command]
+
+ # dsymutil is crazy slow. Let it run for up to a half hour. I hope
+ # that's enough.
+ common.RunSubprocess(dsymutil_command, 30 * 60)
+
+ if saved_test_command:
+ os.rename(saved_test_command, test_command)
+ else:
+ logging.info("No real .dSYM for test_command. Line numbers will "
+ "not be shown. Either tell xcode to generate .dSYM "
+ "file, or use --generate_dsym option to this tool.")
def ValgrindCommand(self):
"""Get the valgrind command to run."""
- raise RuntimeError, "Never use Valgrind directly. Always subclass and " \
- "implement ValgrindCommand() at least"
+ # Note that self._args begins with the exe to be run.
+ tool_name = self.ToolName()
+
+ # Construct the valgrind command.
+ proc = ["valgrind",
+ "--tool=%s" % tool_name,
+ "--smc-check=all",
+ "--num-callers=%i" % self._num_callers]
+
+ if self._options.trace_children:
+ proc += ["--trace-children=yes"];
+
+ proc += self.ToolSpecificFlags()
+ proc += self._tool_flags
+
+ if self._generate_suppressions:
+ proc += ["--gen-suppressions=all"]
+
+ suppression_count = 0
+ for suppression_file in self._suppressions:
+ if os.path.exists(suppression_file):
+ suppression_count += 1
+ proc += ["--suppressions=%s" % suppression_file]
+
+ if not suppression_count:
+ logging.warning("WARNING: NOT USING SUPPRESSIONS!")
+
+ proc += ["--log-file=" + self.TMP_DIR + ("/%s." % tool_name) + "%p"]
+
+ # The Valgrind command is constructed.
+
+ if self._options.indirect:
+ self.CreateBrowserWrapper(" ".join(proc))
+ proc = []
+ proc += self._args
+ return proc
+
+ def ToolSpecificFlags(self):
+ return []
def Execute(self):
''' Execute the app to be tested after successful instrumentation.
@@ -134,28 +257,14 @@ class Valgrind(object):
return True
def Analyze(self):
- # Glob all the files in the "valgrind.tmp" directory
- filenames = glob.glob(self.TMP_DIR + "/valgrind.*")
- # TODO(dkegel): use new xml suppressions feature when it lands
- if self._generate_suppressions:
- # Just concatenate all the output files. Lame...
- for filename in filenames:
- print "## %s" % filename
- f = file(filename)
- while True:
- line = f.readline()
- if len(line) == 0:
- break
- print line, # comma means don't add newline
- f.close()
- return 0
- analyzer = valgrind_analyze.ValgrindAnalyze(self._source_dir, filenames, self._options.show_all_leaks)
- return analyzer.Report()
+ raise RuntimeError, "This method should be implemented " \
+ "in the tool-specific subclass"
def Cleanup(self):
# Right now, we can cleanup by deleting our temporary directory. Other
# cleanup is still a TODO?
- shutil.rmtree(self.TMP_DIR)
+ if not self._nocleanup_on_exit:
+ shutil.rmtree(self.TMP_DIR, ignore_errors=True)
return True
def RunTestsAndAnalyze(self):
@@ -169,11 +278,28 @@ class Valgrind(object):
logging.info("Execution and analysis completed successfully.")
return 0
- def Main(self):
+ def CreateBrowserWrapper(self, command):
+ """The program being run invokes Python or something else
+ that can't stand to be valgrinded, and also invokes
+ the Chrome browser. Set an environment variable to
+ tell the program to prefix the Chrome commandline
+ with a magic wrapper. Build the magic wrapper here.
+ """
+ (fd, indirect_fname) = tempfile.mkstemp(dir=self.TMP_DIR, prefix="browser_wrapper.", text=True)
+ f = os.fdopen(fd, "w");
+ f.write("#!/bin/sh\n")
+ f.write(command)
+ f.write(' "$@"\n')
+ f.close()
+ os.chmod(indirect_fname, stat.S_IRUSR|stat.S_IXUSR)
+ os.putenv("BROWSER_WRAPPER", indirect_fname)
+ logging.info('export BROWSER_WRAPPER=' + indirect_fname);
+
+ def Main(self, args):
'''Call this to run through the whole process: Setup, Execute, Analyze'''
start = datetime.datetime.now()
retcode = -1
- if self.Setup():
+ if self.Setup(args):
retcode = self.RunTestsAndAnalyze()
self.Cleanup()
else:
@@ -187,156 +313,148 @@ class Valgrind(object):
logging.info("elapsed time: %02d:%02d:%02d" % (hours, minutes, seconds))
return retcode
-
-class ValgrindLinux(Valgrind):
-
- """Valgrind on Linux."""
+# TODO(timurrrr): Split into a separate file.
+class Memcheck(ValgrindTool):
+ """Memcheck"""
def __init__(self):
- Valgrind.__init__(self)
-
- def ValgrindCommand(self):
- """Get the valgrind command to run."""
- # note that self._args begins with the exe to be run
-
- if self._options.custom_valgrind_command:
- # take the full valgrind command from --custom_valgrind_command
- proc = self._options.custom_valgrind_command.split()
- else:
- # construct the valgrind command
- proc = ["valgrind", "--smc-check=all", "--leak-check=full",
- "--num-callers=50"]
-
- if self._options.show_all_leaks:
- proc += ["--show-reachable=yes"];
-
- if self._options.track_origins:
- proc += ["--track-origins=yes"];
-
- if self._options.trace_children:
- proc += ["--trace-children=yes"];
-
- # Either generate suppressions or load them.
- # TODO(dkegel): enhance valgrind to support generating
- # suppressions in xml mode. See
- # http://bugs.kde.org/show_bug.cgi?id=191189
- if self._generate_suppressions:
- proc += ["--gen-suppressions=all"]
- else:
- proc += ["--xml=yes"]
-
- suppression_count = 0
- for suppression_file in self._suppressions:
- if os.path.exists(suppression_file):
- suppression_count += 1
- proc += ["--suppressions=%s" % suppression_file]
-
- if not suppression_count:
- logging.warning("WARNING: NOT USING SUPPRESSIONS!")
-
- proc += ["--log-file=" + self.TMP_DIR + "/valgrind.%p"]
-
- # The Valgrind command is constructed.
-
- if self._options.indirect:
- # The program being run invokes Python or something else
- # that can't stand to be valgrinded, and also invokes
- # the Chrome browser. Set an environment variable to
- # tell the program to prefix the Chrome commandline
- # with a magic wrapper. Build the magic wrapper here.
- (fd, indirect_fname) = tempfile.mkstemp(dir=self.TMP_DIR, prefix="browser_wrapper.", text=True)
- f = os.fdopen(fd, "w");
- f.write("#!/bin/sh\n")
- f.write(" ".join(proc))
- f.write(' "$@"\n')
- f.close()
- os.chmod(indirect_fname, stat.S_IRUSR|stat.S_IXUSR)
- os.putenv("BROWSER_WRAPPER", indirect_fname)
- logging.info('export BROWSER_WRAPPER=' + indirect_fname);
- proc = []
-
- proc += self._args
- return proc
-
-
-class ValgrindMac(ValgrindLinux):
-
- """Valgrind on Mac OS X.
- Same as Linux, but currently needs one extra step to run dsymutil.
- This will go away once we update our valgrind.
- """
-
- def PrepareForTest(self):
- """Runs dsymutil if needed.
-
- Valgrind for Mac OS X requires that debugging information be in a .dSYM
- bundle generated by dsymutil. It is not currently able to chase DWARF
- data into .o files like gdb does, so executables without .dSYM bundles or
- with the Chromium-specific "fake_dsym" bundles generated by
- build/mac/strip_save_dsym won't give source file and line number
- information in valgrind.
-
- This function will run dsymutil if the .dSYM bundle is missing or if
- it looks like a fake_dsym. A non-fake dsym that already exists is assumed
- to be up-to-date.
+ ValgrindTool.__init__(self)
+
+ def ToolName(self):
+ return "memcheck"
+
+ def ExtendOptionParser(self, parser):
+ ValgrindTool.ExtendOptionParser(self, parser)
+ parser.add_option("", "--suppressions", default=[],
+ action="append",
+ help="path to a valgrind suppression file")
+ parser.add_option("", "--show_all_leaks", action="store_true",
+ default=False,
+ help="also show less blatant leaks")
+ parser.add_option("", "--track_origins", action="store_true",
+ default=False,
+ help="Show whence uninitialized bytes came. 30% slower.")
+
+ def ToolSpecificFlags(self):
+ ret = ["--leak-check=full"]
+
+ if self._options.show_all_leaks:
+ ret += ["--show-reachable=yes"];
+
+ if self._options.track_origins:
+ ret += ["--track-origins=yes"];
+
+ """Either generate suppressions or load them.
+ TODO(dkegel): enhance valgrind to support generating
+ suppressions in xml mode. See
+ http://bugs.kde.org/show_bug.cgi?id=191189
"""
+ if self._generate_suppressions:
+ ret += ["--gen-suppressions=all"]
+ else:
+ ret += ["--xml=yes"]
- test_command = self._args[0]
- dsym_bundle = self._args[0] + '.dSYM'
- dsym_file = os.path.join(dsym_bundle, 'Contents', 'Resources', 'DWARF',
- os.path.basename(test_command))
- dsym_info_plist = os.path.join(dsym_bundle, 'Contents', 'Info.plist')
+ return ret
- needs_dsymutil = True
- saved_test_command = None
+ def Analyze(self):
+ # Glob all the files in the "valgrind.tmp" directory
+ filenames = glob.glob(self.TMP_DIR + "/memcheck.*")
- if os.path.exists(dsym_file) and os.path.exists(dsym_info_plist):
- # Look for the special fake_dsym tag in dsym_info_plist.
- dsym_info_plist_contents = open(dsym_info_plist).read()
+ # TODO(dkegel): use new xml suppressions feature when it lands
+ if self._generate_suppressions:
+ # Just concatenate all the output files. Lame...
+ for filename in filenames:
+ print "## %s" % filename
+ f = file(filename)
+ while True:
+ line = f.readline()
+ if len(line) == 0:
+ break
+ print line, # comma means don't add newline
+ f.close()
+ return 0
+ analyzer = memcheck_analyze.MemcheckAnalyze(self._source_dir, filenames, self._options.show_all_leaks)
+ return analyzer.Report()
- if not re.search('^\s*<key>fake_dsym</key>$', dsym_info_plist_contents,
- re.MULTILINE):
- # fake_dsym is not set, this is a real .dSYM bundle produced by
- # dsymutil. dsymutil does not need to be run again.
- needs_dsymutil = False
- else:
- # fake_dsym is set. dsym_file is a copy of the original test_command
- # before it was stripped. Copy it back to test_command so that
- # dsymutil has unstripped input to work with. Move the stripped
- # test_command out of the way, it will be restored when this is
- # done.
- saved_test_command = test_command + '.stripped'
- os.rename(test_command, saved_test_command)
- shutil.copyfile(dsym_file, test_command)
- shutil.copymode(saved_test_command, test_command)
+class ThreadSanitizer(ValgrindTool):
+ """ThreadSanitizer"""
- if needs_dsymutil:
- if self._options.generate_dsym:
- # Remove the .dSYM bundle if it exists.
- shutil.rmtree(dsym_bundle, True)
+ def __init__(self):
+ ValgrindTool.__init__(self)
+
+ def ToolName(self):
+ return "tsan"
+
+ def ExtendOptionParser(self, parser):
+ ValgrindTool.ExtendOptionParser(self, parser)
+ parser.add_option("", "--suppressions", default=[],
+ action="append",
+ help="path to a valgrind suppression file")
+ parser.add_option("", "--pure-happens-before", default="yes",
+ dest="pure_happens_before",
+ help="Less false reports, more missed races")
+ parser.add_option("", "--announce-threads", default="yes",
+ dest="announce_threads",
+ help="Show the the stack traces of thread creation")
+
+ def EvalBoolFlag(self, flag_value):
+ if (flag_value in ["1", "true", "yes"]):
+ return True
+ elif (flag_value in ["0", "false", "no"]):
+ return False
+ raise RuntimeError, "Can't parse flag value (%s)" % flag_value
+
+ def ToolSpecificFlags(self):
+ ret = ["--ignore=%s" % \
+ os.path.join(self._source_dir,
+ "tools", "valgrind", "tsan", "ignores.txt")]
+ ret += ["--file-prefix-to-cut=%s/" % self._source_dir]
+
+ if self.EvalBoolFlag(self._options.pure_happens_before):
+ ret += ["--pure-happens-before=yes"];
+
+ if self.EvalBoolFlag(self._options.announce_threads):
+ ret += ["--announce-threads"]
+
+ return ret
- dsymutil_command = ['dsymutil', test_command]
+ def Analyze(self):
+ filenames = glob.glob(self.TMP_DIR + "/tsan.*")
+ analyzer = tsan_analyze.TsanAnalyze(self._source_dir, filenames)
+ return analyzer.Report()
- # dsymutil is crazy slow. Let it run for up to a half hour. I hope
- # that's enough.
- common.RunSubprocess(dsymutil_command, 30 * 60)
- if saved_test_command:
- os.rename(saved_test_command, test_command)
- else:
- logging.info("No real .dSYM for test_command. Line numbers will "
- "not be shown. Either tell xcode to generate .dSYM "
- "file, or use --generate_dsym option to this tool.")
+class ToolFactory:
+ def Create(self, tool_name):
+ if tool_name == "memcheck":
+ return Memcheck()
+ if tool_name == "tsan":
+ if sys.platform != 'linux2':
+ logging.info("WARNING: ThreadSanitizer is not working yet on Mac")
+ return ThreadSanitizer()
+ raise RuntimeError, "Unknown tool" \
+ "(tool=%s, platform=%s)" % \
+ (tool_name, sys.platform)
+
+def RunTool(argv):
+ # TODO(timurrrr): customize optparse instead
+ tool_name = "memcheck"
+ args = argv[1:]
+ for arg in args:
+ if arg.startswith("--tool="):
+ tool_name = arg[7:]
+ args.remove(arg)
+ break
+
+ tool = ToolFactory().Create(tool_name)
+ return tool.Main(args)
if __name__ == "__main__":
- if sys.platform == 'darwin': # Mac
- valgrind = ValgrindMac()
- retcode = valgrind.Main()
- sys.exit(retcode)
- elif sys.platform == 'linux2': # Linux
- valgrind = ValgrindLinux()
- retcode = valgrind.Main()
- sys.exit(retcode)
+ if sys.argv.count("-v") > 0 or sys.argv.count("--verbose") > 0:
+ google.logging_utils.config_root(logging.DEBUG)
else:
- logging.error("Unknown platform: %s" % sys.platform)
- sys.exit(1)
+ google.logging_utils.config_root()
+ # TODO(timurrrr): valgrind tools may use -v/--verbose as well
+
+ ret = RunTool(sys.argv)
+ sys.exit(ret)