Commit ee2bcc4e authored by timurrrr@chromium.org's avatar timurrrr@chromium.org

Set up python BROWSER_WRAPPER for ui_tests under Dr. Memory

Also, clean-up and slightly re-design drmemory_analyze.py to be more like
memcheck_analyze.py
Looks like more refactoring can be done later to really share the code with
Valgrind and TSan, esp. the BROWSER_WRAPPER itself.
No idea why it was a bash script initially.

TBR=bruening,glider
TEST=ran tools\valgrind\chrome_tests.bat --tool drmemory_light --keep_logs -t base --gtest_filter="*Sanity*"
     and ... -t ui --gtest_filter="RedirectTest.Server:RedirectTest.Client"
     with tools/valgrind/drmemory/suppressions.txt removed
Review URL: http://codereview.chromium.org/8688006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@111528 0039d316-1c4b-4281-b951-d872f2087c98
parent 16892aca
# Copyright (c) 2011 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.
import os
import re
import sys
import subprocess
# TODO(timurrrr): we may use it on POSIX too to avoid code duplication once we
# support layout_tests, remove Dr. Memory specific code and verify it works
# on a "clean" Mac.
wrapper_pid = os.getpid()
testcase_name = None
for arg in sys.argv:
m = re.match("\-\-test\-name=(.*)", arg)
if m:
assert testcase_name is None
testcase_name = m.groups()[0]
# arg #0 is the path to this python script
cmd_to_run = sys.argv[1:]
# TODO(timurrrr): this is Dr. Memory-specific
# Usually, we pass "-logdir" "foo\bar\spam path" args to Dr. Memory.
# To group reports per UI test, we want to put the reports for each test into a
# separate directory. This code can be simplified when we have
# http://code.google.com/p/drmemory/issues/detail?id=684 fixed.
logdir_idx = cmd_to_run.index("-logdir")
old_logdir = cmd_to_run[logdir_idx + 1]
cmd_to_run[logdir_idx + 1] += "\\testcase.%d.logs" % wrapper_pid
os.makedirs(cmd_to_run[logdir_idx + 1])
if testcase_name:
f = open(old_logdir + "\\testcase.%d.name" % wrapper_pid, "w")
print >>f, testcase_name
f.close()
exit(subprocess.call(cmd_to_run))
...@@ -162,7 +162,7 @@ def BoringCallers(mangled, use_re_wildcards): ...@@ -162,7 +162,7 @@ def BoringCallers(mangled, use_re_wildcards):
# Don't show our testing framework: # Don't show our testing framework:
("testing::Test::Run", "_ZN7testing4Test3RunEv"), ("testing::Test::Run", "_ZN7testing4Test3RunEv"),
("testing::TestInfo::Run", "_ZN7testing8TestInfo3RunEv"), ("testing::TestInfo::Run", "_ZN7testing8TestInfo3RunEv"),
("testing::internal::Handle*ExceptionsInMethodIfSupported", ("testing::internal::Handle*ExceptionsInMethodIfSupported*",
"_ZN7testing8internal3?Handle*ExceptionsInMethodIfSupported*"), "_ZN7testing8internal3?Handle*ExceptionsInMethodIfSupported*"),
# Depend on scheduling: # Depend on scheduling:
...@@ -212,3 +212,26 @@ def NormalizeWindowsPath(path): ...@@ -212,3 +212,26 @@ def NormalizeWindowsPath(path):
return out.strip() return out.strip()
else: else:
return path return path
############################
# Common output format code
def PrintUsedSuppressionsList(suppcounts):
""" Prints out the list of used suppressions in a format common to all the
memory tools. If the list is empty, prints nothing and returns False,
otherwise True.
suppcounts: a dictionary of used suppression counts,
Key -> name, Value -> count.
"""
if not suppcounts:
return False
print "-----------------------------------------------------"
print "Suppressions used:"
print " count name"
for (name, count) in sorted(suppcounts.items(), key=lambda (k,v): (v,k)):
print "%7d %s" % (count, name)
print "-----------------------------------------------------"
sys.stdout.flush()
return True
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
''' Given a Dr. Memory output file, parses errors and uniques them.''' ''' Given a Dr. Memory output file, parses errors and uniques them.'''
from collections import defaultdict
import common
import logging import logging
import optparse import optparse
import os import os
...@@ -15,30 +17,13 @@ import subprocess ...@@ -15,30 +17,13 @@ import subprocess
import sys import sys
import time import time
class _StackTraceLine(object):
def __init__(self, line, address, binary):
self.raw_line_ = line
self.address = address
self.binary = binary
def __str__(self):
return self.raw_line_
class DrMemoryAnalyze: class DrMemoryAnalyzer:
''' Given a set of Dr.Memory output files, parse all the errors out of ''' Given a set of Dr.Memory output files, parse all the errors out of
them, unique them and output the results.''' them, unique them and output the results.'''
def __init__(self, source_dir, files): def __init__(self):
'''Reads in a set of files. self.known_errors = set()
Args:
source_dir: Path to top of source tree for this build
files: A list of filenames.
'''
self.reports = []
self.used_suppressions = []
for file in files:
self.ParseReportFile(file)
def ReadLine(self): def ReadLine(self):
self.line_ = self.cur_fd_.readline() self.line_ = self.cur_fd_.readline()
...@@ -52,8 +37,9 @@ class DrMemoryAnalyze: ...@@ -52,8 +37,9 @@ class DrMemoryAnalyze:
return result return result
def ParseReportFile(self, filename): def ParseReportFile(self, filename):
self.cur_fd_ = open(filename, 'r') ret = []
self.cur_fd_ = open(filename, 'r')
while True: while True:
self.ReadLine() self.ReadLine()
if (self.line_ == ''): break if (self.line_ == ''): break
...@@ -62,7 +48,7 @@ class DrMemoryAnalyze: ...@@ -62,7 +48,7 @@ class DrMemoryAnalyze:
if match: if match:
self.line_ = match.groups()[0].strip() + "\n" self.line_ = match.groups()[0].strip() + "\n"
tmp = self.ReadSection() tmp = self.ReadSection()
self.reports.append(tmp) ret.append("".join(tmp).strip())
if re.search("SUPPRESSIONS USED:", self.line_): if re.search("SUPPRESSIONS USED:", self.line_):
self.ReadLine() self.ReadLine()
...@@ -70,53 +56,59 @@ class DrMemoryAnalyze: ...@@ -70,53 +56,59 @@ class DrMemoryAnalyze:
line = self.line_.strip() line = self.line_.strip()
(count, name) = re.match(" *([0-9]+)x(?: \(leaked .*\))?: (.*)", (count, name) = re.match(" *([0-9]+)x(?: \(leaked .*\))?: (.*)",
line).groups() line).groups()
self.used_suppressions.append("%7s %s" % (count, name)) count = int(count)
self.used_suppressions[name] += count
self.ReadLine() self.ReadLine()
if self.line_.startswith("ASSERT FAILURE"): if self.line_.startswith("ASSERT FAILURE"):
self.reports.append(self.line_.strip()) ret.append(self.line_.strip())
self.cur_fd_.close() self.cur_fd_.close()
return ret
def Report(self, check_sanity): def Report(self, filenames, testcase, check_sanity):
sys.stdout.flush() sys.stdout.flush()
#TODO(timurrrr): support positive tests / check_sanity==True # TODO(timurrrr): support positive tests / check_sanity==True
if self.used_suppressions: to_report = []
print "-----------------------------------------------------" self.used_suppressions = defaultdict(int)
# TODO(timurrrr): sum up the counts from different wrappers (e.g. ui_tests) for f in filenames:
# or does it work now already? Or add the memcheck-like per-test printing. cur_reports = self.ParseReportFile(f)
print "Suppressions used:\n count name\n%s" % (
"\n".join(self.used_suppressions)) # Filter out the reports that were there in previous tests.
print "-----------------------------------------------------" for r in cur_reports:
sys.stdout.flush() if r in self.known_errors:
pass # TODO: print out a hash once we add hashes to the reports.
if len(self.reports) > 0: else:
logging.error("Found %i error reports" % len(self.reports)) self.known_errors.add(r)
for report_list in self.reports: to_report.append(r)
report = ''
for line in report_list: common.PrintUsedSuppressionsList(self.used_suppressions)
report += str(line)
logging.error('\n' + report) if not to_report:
logging.error("Total: %i error reports" % len(self.reports)) logging.info("PASS: No error reports found")
return -1 return 0
logging.info("PASS: No error reports found")
return 0 logging.error("Found %i error reports" % len(to_report))
for report in to_report:
if testcase:
logging.error("\n%s\nNote: observed on `%s`\n" %
(report, testcase))
else:
logging.error("\n%s\n" % report)
logging.error("Total: %i error reports" % len(to_report))
return -1
if __name__ == '__main__': if __name__ == '__main__':
'''For testing only. The DrMemoryAnalyze class should be imported instead.''' '''For testing only. The DrMemoryAnalyzer class should be imported instead.'''
retcode = 0 retcode = 0
parser = optparse.OptionParser("usage: %prog [options] <files to analyze>") parser = optparse.OptionParser("usage: %prog <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() (options, args) = parser.parse_args()
if len(args) == 0: if len(args) == 0:
parser.error("no filename specified") parser.error("no filename specified")
filenames = args filenames = args
analyzer = DrMemoryAnalyze(options.source_dir, filenames) analyzer = DrMemoryAnalyzer()
retcode = analyzer.Report(False) retcode = analyzer.Report(filenames, None, False)
sys.exit(retcode) sys.exit(retcode)
...@@ -436,7 +436,7 @@ class ValgrindTool(BaseTool): ...@@ -436,7 +436,7 @@ class ValgrindTool(BaseTool):
"in the tool-specific subclass" "in the tool-specific subclass"
def GetAnalyzeResults(self, check_sanity=False): def GetAnalyzeResults(self, check_sanity=False):
# Glob all the files in the "testing.tmp" directory # Glob all the files in the log directory
filenames = glob.glob(self.log_dir + "/" + self.ToolName() + ".*") filenames = glob.glob(self.log_dir + "/" + self.ToolName() + ".*")
# If we have browser wrapper, the logfiles are named as # If we have browser wrapper, the logfiles are named as
...@@ -879,7 +879,9 @@ class DrMemory(BaseTool): ...@@ -879,7 +879,9 @@ class DrMemory(BaseTool):
proc += ["--"] proc += ["--"]
if self._options.indirect: if self._options.indirect:
self.CreateBrowserWrapper(" ".join(proc)) # TODO(timurrrr): reuse for TSan on Windows
self.CreateBrowserWrapper(" ".join(
["python", "tools/valgrind/browser_wrapper_win.py"] + proc))
proc = [] proc = []
# Note that self._args begins with the name of the exe to be run. # Note that self._args begins with the name of the exe to be run.
...@@ -891,11 +893,49 @@ class DrMemory(BaseTool): ...@@ -891,11 +893,49 @@ class DrMemory(BaseTool):
os.putenv("BROWSER_WRAPPER", command) os.putenv("BROWSER_WRAPPER", command)
def Analyze(self, check_sanity=False): def Analyze(self, check_sanity=False):
# Glob all the results files in the "testing.tmp" directory # Use one analyzer for all the log files to avoid printing duplicate reports
filenames = glob.glob(self.log_dir + "/*/results.txt") #
# TODO(timurrrr): unify this with Valgrind and other tools when we have
# http://code.google.com/p/drmemory/issues/detail?id=684
analyzer = drmemory_analyze.DrMemoryAnalyzer()
analyzer = drmemory_analyze.DrMemoryAnalyze(self._source_dir, filenames) ret = 0
ret = analyzer.Report(check_sanity) if not self._options.indirect:
filenames = glob.glob(self.log_dir + "/*/results.txt")
ret = analyzer.Report(filenames, None, check_sanity)
else:
testcases = glob.glob(self.log_dir + "/testcase.*.logs")
# If we have browser wrapper, the per-test logdirs are named as
# "testcase.wrapper_PID".
# Let's extract the list of wrapper_PIDs and name it ppids
ppids = set([int(f.split(".")[-2]) for f in testcases])
for ppid in ppids:
testcase_name = None
try:
f = open(self.log_dir + ("/testcase.%d.name" % ppid))
testcase_name = f.read().strip()
f.close()
except IOError:
pass
print "====================================================="
print " Below is the report for drmemory wrapper PID=%d." % ppid
if testcase_name:
print " It was used while running the `%s` test." % testcase_name
else:
# TODO(timurrrr): hm, the PID line is suppressed on Windows...
print " You can find the corresponding test"
print " by searching the above log for 'PID=%d'" % ppid
sys.stdout.flush()
ppid_filenames = glob.glob("%s/testcase.%d.logs/*/results.txt" %
(self.log_dir, ppid))
ret |= analyzer.Report(ppid_filenames, testcase_name, False)
print "====================================================="
sys.stdout.flush()
logging.info("Please see http://dev.chromium.org/developers/how-tos/"
"using-drmemory for the info on Dr. Memory")
return ret return ret
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment