summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xtools/valgrind/chrome_tests.py60
-rw-r--r--tools/valgrind/memcheck/suppressions.txt (renamed from tools/valgrind/suppressions.txt)0
-rw-r--r--tools/valgrind/memcheck/suppressions_mac.txt (renamed from tools/valgrind/suppressions_mac.txt)0
-rwxr-xr-xtools/valgrind/memcheck_analyze.py (renamed from tools/valgrind/valgrind_analyze.py)14
-rw-r--r--tools/valgrind/tsan/ignores.txt39
-rw-r--r--tools/valgrind/tsan/suppressions.txt1
-rw-r--r--tools/valgrind/tsan_analyze.py85
-rwxr-xr-xtools/valgrind/valgrind.sh59
-rwxr-xr-xtools/valgrind/valgrind_test.py512
9 files changed, 521 insertions, 249 deletions
diff --git a/tools/valgrind/chrome_tests.py b/tools/valgrind/chrome_tests.py
index c7f9e8a..0a65793 100755
--- a/tools/valgrind/chrome_tests.py
+++ b/tools/valgrind/chrome_tests.py
@@ -31,7 +31,7 @@ import google.path_utils
import layout_package.platform_utils
import common
-
+import valgrind_test
class TestNotFound(Exception): pass
@@ -107,8 +107,8 @@ class ChromeTests:
# since this path is used for string matching, make sure it's always
# an absolute Windows-style path
self._source_dir = utility.GetAbsolutePath(self._source_dir)
- valgrind_test = os.path.join(script_dir, "valgrind_test.py")
- self._command_preamble = ["python", valgrind_test,
+ valgrind_test_script = os.path.join(script_dir, "valgrind_test.py")
+ self._command_preamble = [valgrind_test_script,
"--source_dir=%s" % (self._source_dir)]
def _DefaultCommand(self, module, exe=None, valgrind_test_args=None):
@@ -140,7 +140,9 @@ class ChromeTests:
cmd = list(self._command_preamble)
for directory in self._data_dirs:
- suppression_file = os.path.join(directory, "suppressions.txt")
+ tool_name = self._options.valgrind_tool
+ suppression_file = os.path.join(directory,
+ "%s/suppressions.txt" % tool_name)
if os.path.exists(suppression_file):
cmd.append("--suppressions=%s" % suppression_file)
# Platform specific suppression
@@ -149,25 +151,14 @@ class ChromeTests:
'linux2': 'linux'
}[sys.platform]
suppression_file_platform = \
- os.path.join(directory, 'suppressions_%s.txt' % suppression_platform)
+ os.path.join(directory,
+ '%s/suppressions_%s.txt' % (tool_name, suppression_platform))
if os.path.exists(suppression_file_platform):
cmd.append("--suppressions=%s" % suppression_file_platform)
- if self._options.baseline:
- cmd.append("--baseline")
- if self._options.verbose:
- cmd.append("--verbose")
- if self._options.show_all_leaks:
- cmd.append("--show_all_leaks")
- if self._options.track_origins:
- cmd.append("--track_origins")
- if self._options.generate_dsym:
- cmd.append("--generate_dsym")
- if self._options.generate_suppressions:
- cmd.append("--generate_suppressions")
- if self._options.custom_valgrind_command:
- cmd.append("--custom_valgrind_command=%s"
- % self._options.custom_valgrind_command)
+ cmd.append("--tool=%s" % self._options.valgrind_tool)
+ if self._options.valgrind_tool_flags:
+ cmd += self._options.valgrind_tool_flags.split(" ")
if valgrind_test_args != None:
for arg in valgrind_test_args:
cmd.append(arg)
@@ -193,7 +184,11 @@ class ChromeTests:
'linux2': 'linux'}[sys.platform]
gtest_filter_files = [
os.path.join(directory, name + ".gtest.txt"),
- os.path.join(directory, name + ".gtest_%s.txt" % platform_suffix)]
+ os.path.join(directory, name + ".gtest-%s.txt" % \
+ self._options.valgrind_tool),
+ os.path.join(directory, name + ".gtest_%s.txt" % platform_suffix),
+ os.path.join(directory, name + ".gtest-%s_%s.txt" % \
+ (self._options.valgrind_tool, platform_suffix))]
for filename in gtest_filter_files:
if os.path.exists(filename):
logging.info("reading gtest filters from %s" % filename)
@@ -221,7 +216,7 @@ class ChromeTests:
if cmd_args:
cmd.extend(["--"])
cmd.extend(cmd_args)
- return common.RunSubprocess(cmd, 0)
+ return valgrind_test.RunTool(cmd)
def TestBase(self):
return self.SimpleTest("base", "base_unittests")
@@ -324,8 +319,7 @@ class ChromeTests:
# Now run script_cmd with the wrapper in cmd
cmd.extend(["--"])
cmd.extend(script_cmd)
- ret = common.RunSubprocess(cmd, 0)
- return ret
+ return valgrind_test.RunTool(cmd)
def TestLayout(self):
# A "chunk file" is maintained in the local directory so that each test
@@ -387,20 +381,10 @@ def _main(_):
help="additional arguments to --gtest_filter")
parser.add_option("-v", "--verbose", action="store_true", default=False,
help="verbose output - enable debug log messages")
- parser.add_option("", "--show_all_leaks", action="store_true",
- default=False,
- help="also show even less blatant leaks")
- parser.add_option("", "--track_origins", action="store_true",
- default=False,
- help="Show whence uninit bytes came. 30% slower.")
- parser.add_option("", "--generate_dsym", action="store_true",
- default=False,
- help="Generate .dSYM file on Mac if needed. Slow!")
- parser.add_option("", "--generate_suppressions", action="store_true",
- default=False,
- help="Skip analysis and generate suppressions")
- parser.add_option("", "--custom_valgrind_command",
- help="Use custom valgrind command and options")
+ parser.add_option("", "--tool", dest="valgrind_tool", default="memcheck",
+ help="specify a valgrind tool to run the tests under")
+ parser.add_option("", "--tool_flags", dest="valgrind_tool_flags", default="",
+ help="specify custom flags for the selected valgrind tool")
# My machine can do about 120 layout tests/hour in release mode.
# Let's do 30 minutes worth per run.
# The CPU is mostly idle, so perhaps we can raise this when
diff --git a/tools/valgrind/suppressions.txt b/tools/valgrind/memcheck/suppressions.txt
index 9cac256..9cac256 100644
--- a/tools/valgrind/suppressions.txt
+++ b/tools/valgrind/memcheck/suppressions.txt
diff --git a/tools/valgrind/suppressions_mac.txt b/tools/valgrind/memcheck/suppressions_mac.txt
index 5eb9261..5eb9261 100644
--- a/tools/valgrind/suppressions_mac.txt
+++ b/tools/valgrind/memcheck/suppressions_mac.txt
diff --git a/tools/valgrind/valgrind_analyze.py b/tools/valgrind/memcheck_analyze.py
index 9fa0f12..a108d36 100755
--- a/tools/valgrind/valgrind_analyze.py
+++ b/tools/valgrind/memcheck_analyze.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.
-# valgrind_analyze.py
+# memcheck_analyze.py
''' Given a valgrind XML file, parses errors and uniques them.'''
@@ -176,7 +176,7 @@ class ValgrindError:
def __eq__(self, rhs):
return self.UniqueString() == rhs
-class ValgrindAnalyze:
+class MemcheckAnalyze:
''' Given a set of Valgrind XML files, parse all the errors out of them,
unique them and output the results.'''
@@ -192,6 +192,7 @@ class ValgrindAnalyze:
self._errors = set()
badfiles = set()
start = time.time()
+ self._parse_failed = False
for file in files:
# Wait up to three minutes for valgrind to finish writing all files,
# but after that, just skip incomplete files and warn.
@@ -217,6 +218,7 @@ class ValgrindAnalyze:
getTextOf(raw_error, "kind") != "Leak_PossiblyLost"):
self._errors.add(ValgrindError(source_dir, raw_error))
except ExpatError, e:
+ self._parse_failed = True
logging.warn("could not parse %s: %s" % (file, e))
lineno = e.lineno - 1
context_lines = 5
@@ -237,6 +239,10 @@ class ValgrindAnalyze:
logging.warn("valgrind didn't finish writing %d files?!" % len(badfiles))
def Report(self):
+ if self._parse_failed:
+ logging.error("FAIL! Couldn't parse Valgrind output file")
+ return -2
+
if self._errors:
logging.error("FAIL! There were %s errors: " % len(self._errors))
@@ -249,7 +255,7 @@ class ValgrindAnalyze:
return 0
def _main():
- '''For testing only. The ValgrindAnalyze class should be imported instead.'''
+ '''For testing only. The MemcheckAnalyze class should be imported instead.'''
retcode = 0
parser = optparse.OptionParser("usage: %prog [options] <files to analyze>")
parser.add_option("", "--source_dir",
@@ -261,7 +267,7 @@ def _main():
parser.error("no filename specified")
filenames = args
- analyzer = ValgrindAnalyze(options.source_dir, filenames)
+ analyzer = MemcheckAnalyze(options.source_dir, filenames)
retcode = analyzer.Report()
sys.exit(retcode)
diff --git a/tools/valgrind/tsan/ignores.txt b/tools/valgrind/tsan/ignores.txt
new file mode 100644
index 0000000..e1f0972
--- /dev/null
+++ b/tools/valgrind/tsan/ignores.txt
@@ -0,0 +1,39 @@
+# This file lists the functions, object files and source files
+# which should be ignored (i.e. not instrumented) by ThreadSanitizer.
+
+obj:*/ld-2*
+obj:*/libpthread-*
+
+fun:clone
+fun:fork
+fun:pthread_*
+fun:__lll_mutex_unlock_wake
+fun:__cxa_atexit
+fun:__new_exitfn
+
+src:*ts_valgrind_intercepts.c
+
+# Chromium-specific
+fun:*_ZN4base6subtle*
+
+
+# dark magic with 'errno' here.
+#fun:sys_ptrace_detach
+#fun:sys_ptrace
+#fun:sys_stat
+fun:sys_*
+
+# ignore libc's printf functions
+fun:_IO_*
+fun:vfprintf
+fun:fwrite
+fun:fflush
+
+
+# 32-bit version of std::string
+# There is something very strange inside this function.
+# Looks like they write zero to a global variable which
+# value is already zero. I.e. the race is benign.
+# Global var is _ZNSs4_Rep20_S_empty_rep_storageE
+fun:*_M_mutateEjjj
+
diff --git a/tools/valgrind/tsan/suppressions.txt b/tools/valgrind/tsan/suppressions.txt
new file mode 100644
index 0000000..c0e0008
--- /dev/null
+++ b/tools/valgrind/tsan/suppressions.txt
@@ -0,0 +1 @@
+# There are no suppressions here yet.
diff --git a/tools/valgrind/tsan_analyze.py b/tools/valgrind/tsan_analyze.py
new file mode 100644
index 0000000..13f3bc7
--- /dev/null
+++ b/tools/valgrind/tsan_analyze.py
@@ -0,0 +1,85 @@
+#!/usr/bin/python
+# Copyright (c) 2006-2008 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.
+
+# tsan_analyze.py
+
+''' Given a ThreadSanitizer output file, parses errors and uniques them.'''
+
+import logging
+import optparse
+import os
+import re
+import subprocess
+import sys
+import time
+
+class TsanAnalyze:
+ ''' Given a set of ThreadSanitizer output files, parse all the errors out of
+ them, unique them and output the results.'''
+
+ def __init__(self, source_dir, files):
+ '''Reads in a set of files.
+
+ Args:
+ source_dir: Path to top of source tree for this build
+ files: A list of filenames.
+ '''
+
+ self.races = []
+ for file in files:
+ self.races = self.races + self.GetReportFrom(file)
+
+ def GetReportFrom(self, filename):
+ ret = []
+ f = open(filename, 'r')
+ while True:
+ line = f.readline()
+ if (line == ''):
+ break
+ tmp = ""
+ while (re.match(".*INFO: T.* " +
+ "(has been created by T.* at this point|is program's main thread)"
+ ".*", line)):
+ tmp = tmp + line
+ while not re.match(".*}}}.*", line):
+ line = f.readline()
+ tmp = tmp + line
+ line = f.readline()
+
+ if (re.match(".*Possible data race.*", line)):
+ tmp = tmp + line
+ while not re.match(".*}}}.*", line):
+ line = f.readline()
+ tmp = tmp + line
+ ret.append(tmp.strip())
+ f.close()
+ return ret
+
+ def Report(self):
+ if len(self.races) > 0:
+ logging.error("Found %i race reports" % len(self.races))
+ for report in self.races:
+ logging.error("\n" + report)
+ return -1
+ logging.info("PASS: No race reports found")
+ return 0
+
+if __name__ == '__main__':
+ '''For testing only. The TsanAnalyze class should be imported instead.'''
+ retcode = 0
+ parser = optparse.OptionParser("usage: %prog [options] <files to analyze>")
+ parser.add_option("", "--source_dir",
+ help="path to top of source tree for this build"
+ "(used to normalize source paths in baseline)")
+
+ (options, args) = parser.parse_args()
+ if not len(args) >= 1:
+ parser.error("no filename specified")
+ filenames = args
+
+ analyzer = TsanAnalyze(options.source_dir, filenames)
+ retcode = analyzer.Report()
+
+ sys.exit(retcode)
diff --git a/tools/valgrind/valgrind.sh b/tools/valgrind/valgrind.sh
index 0170038..94e571e 100755
--- a/tools/valgrind/valgrind.sh
+++ b/tools/valgrind/valgrind.sh
@@ -12,6 +12,31 @@
# To run unit tests, you probably want to run chrome_tests.sh instead.
# That's the script used by the valgrind buildbot.
+setup_memcheck() {
+ # Prefer a 32-bit gdb if it's available.
+ GDB="/usr/bin/gdb32";
+ if [ ! -x $GDB ]; then
+ GDB="gdb"
+ fi
+
+ # Prompt to attach gdb when there was an error detected.
+ DEFAULT_TOOL_FLAGS=("--db-command=$GDB -nw %f %p" "--db-attach=yes" \
+ # Overwrite newly allocated or freed objects
+ # with 0x41 to catch inproper use.
+ "--malloc-fill=41" "--free-fill=41")
+}
+
+setup_tsan() {
+ IGNORE_FILE="$(cd `dirname "$0"` && pwd)/tsan/ignores.txt"
+ DEFAULT_TOOL_FLAGS=("--announce-threads" "--pure-happens-before=yes" \
+ "--ignore=$IGNORE_FILE")
+}
+
+setup_unknown() {
+ echo "Unknown tool \"$TOOL_NAME\" specified, the result is not guaranteed"
+ DEFAULT_TOOL_FLAGS=()
+}
+
set -e
if [ $# -eq 0 ]; then
@@ -19,21 +44,36 @@ if [ $# -eq 0 ]; then
exit 1
fi
-# Prefer a 32-bit gdb if it's available.
-GDB="/usr/bin/gdb32";
-if [ ! -x $GDB ]; then
- GDB="gdb"
+TOOL_NAME="memcheck"
+declare -a DEFAULT_TOOL_FLAGS[0]
+
+# Select a tool different from memcheck with --tool=TOOL as a first argument
+TMP_STR=`echo $1 | sed 's/^\-\-tool=//'`
+if [ "$TMP_STR" != "$1" ]; then
+ TOOL_NAME="$TMP_STR"
+ shift
+fi
+
+if echo "$@" | grep "\-\-tool" ; then
+ echo "--tool=TOOL must be the first argument" >&2
+ exit 1
fi
+case $TOOL_NAME in
+ memcheck*) setup_memcheck;;
+ tsan*) setup_tsan;;
+ *) setup_unknown;;
+esac
+
# Prefer a local install of valgrind if it's available.
VALGRIND="/usr/local/bin/valgrind"
if [ ! -x $VALGRIND ]; then
VALGRIND="valgrind"
fi
-SUPPRESSIONS="$(cd `dirname "$0"` && pwd)/suppressions.txt"
+SUPPRESSIONS="$(cd `dirname "$0"` && pwd)/$TOOL_NAME/suppressions.txt"
-set -v
+set -x
# Pass GTK glib allocations through to system malloc so valgrind sees them.
# Prevent NSS from recycling memory arenas so valgrind can track origins.
@@ -41,15 +81,14 @@ set -v
# Overwrite newly allocated or freed objects with 0x41 to catch inproper use.
# smc-check=all is required for valgrind to see v8's dynamic code generation.
# trace-children to follow into the renderer processes.
-# Prompt to attach gdb when there was an error detected.
+
G_SLICE=always-malloc \
NSS_DISABLE_ARENA_FREE_LIST=1 \
G_DEBUG=fatal_warnings \
"$VALGRIND" \
+ --tool=$TOOL_NAME \
--trace-children=yes \
- --db-command="$GDB -nw %f %p" \
- --db-attach=yes \
--suppressions="$SUPPRESSIONS" \
- --malloc-fill=41 --free-fill=41 \
--smc-check=all \
+ "${DEFAULT_TOOL_FLAGS[@]}" \
"$@"
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)