Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1282)

Side by Side Diff: tools/valgrind/valgrind_test.py

Issue 2062813002: Remove some scripts that were used by the valgrind bots. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebsae Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tools/valgrind/valgrind.sh ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Runs an exe through Valgrind and puts the intermediate files in a
6 directory.
7 """
8
9 import datetime
10 import glob
11 import logging
12 import optparse
13 import os
14 import re
15 import shutil
16 import stat
17 import subprocess
18 import sys
19 import tempfile
20
21 import common
22
23 import drmemory_analyze
24 import memcheck_analyze
25
26 class BaseTool(object):
27 """Abstract class for running dynamic error detection tools.
28
29 Always subclass this and implement ToolCommand with framework- and
30 tool-specific stuff.
31 """
32
33 def __init__(self):
34 temp_parent_dir = None
35 self.log_parent_dir = ""
36 if common.IsWindows():
37 # gpu process on Windows Vista+ runs at Low Integrity and can only
38 # write to certain directories (http://crbug.com/119131)
39 #
40 # TODO(bruening): if scripts die in middle and don't clean up temp
41 # dir, we'll accumulate files in profile dir. should remove
42 # really old files automatically.
43 profile = os.getenv("USERPROFILE")
44 if profile:
45 self.log_parent_dir = profile + "\\AppData\\LocalLow\\"
46 if os.path.exists(self.log_parent_dir):
47 self.log_parent_dir = common.NormalizeWindowsPath(self.log_parent_dir)
48 temp_parent_dir = self.log_parent_dir
49 # Generated every time (even when overridden)
50 self.temp_dir = tempfile.mkdtemp(prefix="vg_logs_", dir=temp_parent_dir)
51 self.log_dir = self.temp_dir # overridable by --keep_logs
52 self.option_parser_hooks = []
53 # TODO(glider): we may not need some of the env vars on some of the
54 # platforms.
55 self._env = {
56 "G_SLICE" : "always-malloc",
57 "NSS_DISABLE_UNLOAD" : "1",
58 "NSS_DISABLE_ARENA_FREE_LIST" : "1",
59 "GTEST_DEATH_TEST_USE_FORK": "1",
60 }
61
62 def ToolName(self):
63 raise NotImplementedError, "This method should be implemented " \
64 "in the tool-specific subclass"
65
66 def Analyze(self, check_sanity=False):
67 raise NotImplementedError, "This method should be implemented " \
68 "in the tool-specific subclass"
69
70 def RegisterOptionParserHook(self, hook):
71 # Frameworks and tools can add their own flags to the parser.
72 self.option_parser_hooks.append(hook)
73
74 def CreateOptionParser(self):
75 # Defines Chromium-specific flags.
76 self._parser = optparse.OptionParser("usage: %prog [options] <program to "
77 "test>")
78 self._parser.disable_interspersed_args()
79 self._parser.add_option("-t", "--timeout",
80 dest="timeout", metavar="TIMEOUT", default=10000,
81 help="timeout in seconds for the run (default 10000)")
82 self._parser.add_option("", "--build-dir",
83 help="the location of the compiler output")
84 self._parser.add_option("", "--source-dir",
85 help="path to top of source tree for this build"
86 "(used to normalize source paths in baseline)")
87 self._parser.add_option("", "--gtest_filter", default="",
88 help="which test case to run")
89 self._parser.add_option("", "--gtest_repeat",
90 help="how many times to run each test")
91 self._parser.add_option("", "--gtest_print_time", action="store_true",
92 default=False,
93 help="show how long each test takes")
94 self._parser.add_option("", "--ignore_exit_code", action="store_true",
95 default=False,
96 help="ignore exit code of the test "
97 "(e.g. test failures)")
98 self._parser.add_option("", "--keep_logs", action="store_true",
99 default=False,
100 help="store memory tool logs in the <tool>.logs "
101 "directory instead of /tmp.\nThis can be "
102 "useful for tool developers/maintainers.\n"
103 "Please note that the <tool>.logs directory "
104 "will be clobbered on tool startup.")
105
106 # To add framework- or tool-specific flags, please add a hook using
107 # RegisterOptionParserHook in the corresponding subclass.
108 # See ValgrindTool for an example.
109 for hook in self.option_parser_hooks:
110 hook(self, self._parser)
111
112 def ParseArgv(self, args):
113 self.CreateOptionParser()
114
115 # self._tool_flags will store those tool flags which we don't parse
116 # manually in this script.
117 self._tool_flags = []
118 known_args = []
119
120 """ We assume that the first argument not starting with "-" is a program
121 name and all the following flags should be passed to the program.
122 TODO(timurrrr): customize optparse instead
123 """
124 while len(args) > 0 and args[0][:1] == "-":
125 arg = args[0]
126 if (arg == "--"):
127 break
128 if self._parser.has_option(arg.split("=")[0]):
129 known_args += [arg]
130 else:
131 self._tool_flags += [arg]
132 args = args[1:]
133
134 if len(args) > 0:
135 known_args += args
136
137 self._options, self._args = self._parser.parse_args(known_args)
138
139 self._timeout = int(self._options.timeout)
140 self._source_dir = self._options.source_dir
141 if self._options.keep_logs:
142 # log_parent_dir has trailing slash if non-empty
143 self.log_dir = self.log_parent_dir + "%s.logs" % self.ToolName()
144 if os.path.exists(self.log_dir):
145 shutil.rmtree(self.log_dir)
146 os.mkdir(self.log_dir)
147 logging.info("Logs are in " + self.log_dir)
148
149 self._ignore_exit_code = self._options.ignore_exit_code
150 if self._options.gtest_filter != "":
151 self._args.append("--gtest_filter=%s" % self._options.gtest_filter)
152 if self._options.gtest_repeat:
153 self._args.append("--gtest_repeat=%s" % self._options.gtest_repeat)
154 if self._options.gtest_print_time:
155 self._args.append("--gtest_print_time")
156
157 return True
158
159 def Setup(self, args):
160 return self.ParseArgv(args)
161
162 def ToolCommand(self):
163 raise NotImplementedError, "This method should be implemented " \
164 "in the tool-specific subclass"
165
166 def Cleanup(self):
167 # You may override it in the tool-specific subclass
168 pass
169
170 def Execute(self):
171 """ Execute the app to be tested after successful instrumentation.
172 Full execution command-line provided by subclassers via proc."""
173 logging.info("starting execution...")
174 proc = self.ToolCommand()
175 for var in self._env:
176 common.PutEnvAndLog(var, self._env[var])
177 return common.RunSubprocess(proc, self._timeout)
178
179 def RunTestsAndAnalyze(self, check_sanity):
180 exec_retcode = self.Execute()
181 analyze_retcode = self.Analyze(check_sanity)
182
183 if analyze_retcode:
184 logging.error("Analyze failed.")
185 logging.info("Search the log for '[ERROR]' to see the error reports.")
186 return analyze_retcode
187
188 if exec_retcode:
189 if self._ignore_exit_code:
190 logging.info("Test execution failed, but the exit code is ignored.")
191 else:
192 logging.error("Test execution failed.")
193 return exec_retcode
194 else:
195 logging.info("Test execution completed successfully.")
196
197 if not analyze_retcode:
198 logging.info("Analysis completed successfully.")
199
200 return 0
201
202 def Main(self, args, check_sanity, min_runtime_in_seconds):
203 """Call this to run through the whole process: Setup, Execute, Analyze"""
204 start_time = datetime.datetime.now()
205 retcode = -1
206 if self.Setup(args):
207 retcode = self.RunTestsAndAnalyze(check_sanity)
208 shutil.rmtree(self.temp_dir, ignore_errors=True)
209 self.Cleanup()
210 else:
211 logging.error("Setup failed")
212 end_time = datetime.datetime.now()
213 runtime_in_seconds = (end_time - start_time).seconds
214 hours = runtime_in_seconds / 3600
215 seconds = runtime_in_seconds % 3600
216 minutes = seconds / 60
217 seconds = seconds % 60
218 logging.info("elapsed time: %02d:%02d:%02d" % (hours, minutes, seconds))
219 if (min_runtime_in_seconds > 0 and
220 runtime_in_seconds < min_runtime_in_seconds):
221 logging.error("Layout tests finished too quickly. "
222 "It should have taken at least %d seconds. "
223 "Something went wrong?" % min_runtime_in_seconds)
224 retcode = -1
225 return retcode
226
227 def Run(self, args, module, min_runtime_in_seconds=0):
228 MODULES_TO_SANITY_CHECK = ["base"]
229
230 check_sanity = module in MODULES_TO_SANITY_CHECK
231 return self.Main(args, check_sanity, min_runtime_in_seconds)
232
233
234 class ValgrindTool(BaseTool):
235 """Abstract class for running Valgrind tools.
236
237 Always subclass this and implement ToolSpecificFlags() and
238 ExtendOptionParser() for tool-specific stuff.
239 """
240 def __init__(self):
241 super(ValgrindTool, self).__init__()
242 self.RegisterOptionParserHook(ValgrindTool.ExtendOptionParser)
243
244 def UseXML(self):
245 # Override if tool prefers nonxml output
246 return True
247
248 def ExtendOptionParser(self, parser):
249 parser.add_option("", "--suppressions", default=[],
250 action="append",
251 help="path to a valgrind suppression file")
252 parser.add_option("", "--indirect", action="store_true",
253 default=False,
254 help="set BROWSER_WRAPPER rather than "
255 "running valgrind directly")
256 parser.add_option("", "--indirect_webkit_layout", action="store_true",
257 default=False,
258 help="set --wrapper rather than running Dr. Memory "
259 "directly.")
260 parser.add_option("", "--trace_children", action="store_true",
261 default=False,
262 help="also trace child processes")
263 parser.add_option("", "--num-callers",
264 dest="num_callers", default=30,
265 help="number of callers to show in stack traces")
266 parser.add_option("", "--generate_dsym", action="store_true",
267 default=False,
268 help="Generate .dSYM file on Mac if needed. Slow!")
269
270 def Setup(self, args):
271 if not BaseTool.Setup(self, args):
272 return False
273 if common.IsMac():
274 self.PrepareForTestMac()
275 return True
276
277 def PrepareForTestMac(self):
278 """Runs dsymutil if needed.
279
280 Valgrind for Mac OS X requires that debugging information be in a .dSYM
281 bundle generated by dsymutil. It is not currently able to chase DWARF
282 data into .o files like gdb does, so executables without .dSYM bundles or
283 with the Chromium-specific "fake_dsym" bundles generated by
284 build/mac/strip_save_dsym won't give source file and line number
285 information in valgrind.
286
287 This function will run dsymutil if the .dSYM bundle is missing or if
288 it looks like a fake_dsym. A non-fake dsym that already exists is assumed
289 to be up to date.
290 """
291 test_command = self._args[0]
292 dsym_bundle = self._args[0] + '.dSYM'
293 dsym_file = os.path.join(dsym_bundle, 'Contents', 'Resources', 'DWARF',
294 os.path.basename(test_command))
295 dsym_info_plist = os.path.join(dsym_bundle, 'Contents', 'Info.plist')
296
297 needs_dsymutil = True
298 saved_test_command = None
299
300 if os.path.exists(dsym_file) and os.path.exists(dsym_info_plist):
301 # Look for the special fake_dsym tag in dsym_info_plist.
302 dsym_info_plist_contents = open(dsym_info_plist).read()
303
304 if not re.search('^\s*<key>fake_dsym</key>$', dsym_info_plist_contents,
305 re.MULTILINE):
306 # fake_dsym is not set, this is a real .dSYM bundle produced by
307 # dsymutil. dsymutil does not need to be run again.
308 needs_dsymutil = False
309 else:
310 # fake_dsym is set. dsym_file is a copy of the original test_command
311 # before it was stripped. Copy it back to test_command so that
312 # dsymutil has unstripped input to work with. Move the stripped
313 # test_command out of the way, it will be restored when this is
314 # done.
315 saved_test_command = test_command + '.stripped'
316 os.rename(test_command, saved_test_command)
317 shutil.copyfile(dsym_file, test_command)
318 shutil.copymode(saved_test_command, test_command)
319
320 if needs_dsymutil:
321 if self._options.generate_dsym:
322 # Remove the .dSYM bundle if it exists.
323 shutil.rmtree(dsym_bundle, True)
324
325 dsymutil_command = ['dsymutil', test_command]
326
327 # dsymutil is crazy slow. Ideally we'd have a timeout here,
328 # but common.RunSubprocess' timeout is only checked
329 # after each line of output; dsymutil is silent
330 # until the end, and is then killed, which is silly.
331 common.RunSubprocess(dsymutil_command)
332
333 if saved_test_command:
334 os.rename(saved_test_command, test_command)
335 else:
336 logging.info("No real .dSYM for test_command. Line numbers will "
337 "not be shown. Either tell xcode to generate .dSYM "
338 "file, or use --generate_dsym option to this tool.")
339
340 def ToolCommand(self):
341 """Get the valgrind command to run."""
342 # Note that self._args begins with the exe to be run.
343 tool_name = self.ToolName()
344
345 # Construct the valgrind command.
346 if 'CHROME_VALGRIND' in os.environ:
347 path = os.path.join(os.environ['CHROME_VALGRIND'], "bin", "valgrind")
348 else:
349 path = "valgrind"
350 proc = [path, "--tool=%s" % tool_name]
351
352 proc += ["--num-callers=%i" % int(self._options.num_callers)]
353
354 if self._options.trace_children:
355 proc += ["--trace-children=yes"]
356 proc += ["--trace-children-skip='*dbus-daemon*'"]
357 proc += ["--trace-children-skip='*dbus-launch*'"]
358 proc += ["--trace-children-skip='*perl*'"]
359 proc += ["--trace-children-skip='*python*'"]
360 # This is really Python, but for some reason Valgrind follows it.
361 proc += ["--trace-children-skip='*lsb_release*'"]
362
363 proc += self.ToolSpecificFlags()
364 proc += self._tool_flags
365
366 suppression_count = 0
367 for suppression_file in self._options.suppressions:
368 if os.path.exists(suppression_file):
369 suppression_count += 1
370 proc += ["--suppressions=%s" % suppression_file]
371
372 if not suppression_count:
373 logging.warning("WARNING: NOT USING SUPPRESSIONS!")
374
375 logfilename = self.log_dir + ("/%s." % tool_name) + "%p"
376 if self.UseXML():
377 proc += ["--xml=yes", "--xml-file=" + logfilename]
378 else:
379 proc += ["--log-file=" + logfilename]
380
381 # The Valgrind command is constructed.
382
383 # Handle --indirect_webkit_layout separately.
384 if self._options.indirect_webkit_layout:
385 # Need to create the wrapper before modifying |proc|.
386 wrapper = self.CreateBrowserWrapper(proc, webkit=True)
387 proc = self._args
388 proc.append("--wrapper")
389 proc.append(wrapper)
390 return proc
391
392 if self._options.indirect:
393 wrapper = self.CreateBrowserWrapper(proc)
394 os.environ["BROWSER_WRAPPER"] = wrapper
395 logging.info('export BROWSER_WRAPPER=' + wrapper)
396 proc = []
397 proc += self._args
398 return proc
399
400 def ToolSpecificFlags(self):
401 raise NotImplementedError, "This method should be implemented " \
402 "in the tool-specific subclass"
403
404 def CreateBrowserWrapper(self, proc, webkit=False):
405 """The program being run invokes Python or something else that can't stand
406 to be valgrinded, and also invokes the Chrome browser. In this case, use a
407 magic wrapper to only valgrind the Chrome browser. Build the wrapper here.
408 Returns the path to the wrapper. It's up to the caller to use the wrapper
409 appropriately.
410 """
411 command = " ".join(proc)
412 # Add the PID of the browser wrapper to the logfile names so we can
413 # separate log files for different UI tests at the analyze stage.
414 command = command.replace("%p", "$$.%p")
415
416 (fd, indirect_fname) = tempfile.mkstemp(dir=self.log_dir,
417 prefix="browser_wrapper.",
418 text=True)
419 f = os.fdopen(fd, "w")
420 f.write('#!/bin/bash\n'
421 'echo "Started Valgrind wrapper for this test, PID=$$" >&2\n')
422
423 f.write('DIR=`dirname $0`\n'
424 'TESTNAME_FILE=$DIR/testcase.$$.name\n\n')
425
426 if webkit:
427 # Webkit layout_tests pass the URL as the first line of stdin.
428 f.write('tee $TESTNAME_FILE | %s "$@"\n' % command)
429 else:
430 # Try to get the test case name by looking at the program arguments.
431 # i.e. Chromium ui_tests used --test-name arg.
432 # TODO(timurrrr): This doesn't handle "--test-name Test.Name"
433 # TODO(timurrrr): ui_tests are dead. Where do we use the non-webkit
434 # wrapper now? browser_tests? What do they do?
435 f.write('for arg in $@\ndo\n'
436 ' if [[ "$arg" =~ --test-name=(.*) ]]\n then\n'
437 ' echo ${BASH_REMATCH[1]} >$TESTNAME_FILE\n'
438 ' fi\n'
439 'done\n\n'
440 '%s "$@"\n' % command)
441
442 f.close()
443 os.chmod(indirect_fname, stat.S_IRUSR|stat.S_IXUSR)
444 return indirect_fname
445
446 def CreateAnalyzer(self):
447 raise NotImplementedError, "This method should be implemented " \
448 "in the tool-specific subclass"
449
450 def GetAnalyzeResults(self, check_sanity=False):
451 # Glob all the files in the log directory
452 filenames = glob.glob(self.log_dir + "/" + self.ToolName() + ".*")
453
454 # If we have browser wrapper, the logfiles are named as
455 # "toolname.wrapper_PID.valgrind_PID".
456 # Let's extract the list of wrapper_PIDs and name it ppids
457 ppids = set([int(f.split(".")[-2]) \
458 for f in filenames if re.search("\.[0-9]+\.[0-9]+$", f)])
459
460 analyzer = self.CreateAnalyzer()
461 if len(ppids) == 0:
462 # Fast path - no browser wrapper was set.
463 return analyzer.Report(filenames, None, check_sanity)
464
465 ret = 0
466 for ppid in ppids:
467 testcase_name = None
468 try:
469 f = open(self.log_dir + ("/testcase.%d.name" % ppid))
470 testcase_name = f.read().strip()
471 f.close()
472 wk_layout_prefix="third_party/WebKit/LayoutTests/"
473 wk_prefix_at = testcase_name.rfind(wk_layout_prefix)
474 if wk_prefix_at != -1:
475 testcase_name = testcase_name[wk_prefix_at + len(wk_layout_prefix):]
476 except IOError:
477 pass
478 print "====================================================="
479 print " Below is the report for valgrind wrapper PID=%d." % ppid
480 if testcase_name:
481 print " It was used while running the `%s` test." % testcase_name
482 else:
483 print " You can find the corresponding test"
484 print " by searching the above log for 'PID=%d'" % ppid
485 sys.stdout.flush()
486
487 ppid_filenames = [f for f in filenames \
488 if re.search("\.%d\.[0-9]+$" % ppid, f)]
489 # check_sanity won't work with browser wrappers
490 assert check_sanity == False
491 ret |= analyzer.Report(ppid_filenames, testcase_name)
492 print "====================================================="
493 sys.stdout.flush()
494
495 if ret != 0:
496 print ""
497 print "The Valgrind reports are grouped by test names."
498 print "Each test has its PID printed in the log when the test was run"
499 print "and at the beginning of its Valgrind report."
500 print "Hint: you can search for the reports by Ctrl+F -> `=#`"
501 sys.stdout.flush()
502
503 return ret
504
505
506 # TODO(timurrrr): Split into a separate file.
507 class Memcheck(ValgrindTool):
508 """Memcheck
509 Dynamic memory error detector for Linux & Mac
510
511 http://valgrind.org/info/tools.html#memcheck
512 """
513
514 def __init__(self):
515 super(Memcheck, self).__init__()
516 self.RegisterOptionParserHook(Memcheck.ExtendOptionParser)
517
518 def ToolName(self):
519 return "memcheck"
520
521 def ExtendOptionParser(self, parser):
522 parser.add_option("--leak-check", "--leak_check", type="string",
523 default="yes", # --leak-check=yes is equivalent of =full
524 help="perform leak checking at the end of the run")
525 parser.add_option("", "--show_all_leaks", action="store_true",
526 default=False,
527 help="also show less blatant leaks")
528 parser.add_option("", "--track_origins", action="store_true",
529 default=False,
530 help="Show whence uninitialized bytes came. 30% slower.")
531
532 def ToolSpecificFlags(self):
533 ret = ["--gen-suppressions=all", "--demangle=no"]
534 ret += ["--leak-check=%s" % self._options.leak_check]
535
536 if self._options.show_all_leaks:
537 ret += ["--show-reachable=yes"]
538 else:
539 ret += ["--show-possibly-lost=no"]
540
541 if self._options.track_origins:
542 ret += ["--track-origins=yes"]
543
544 # TODO(glider): this is a temporary workaround for http://crbug.com/51716
545 # Let's see whether it helps.
546 if common.IsMac():
547 ret += ["--smc-check=all"]
548
549 return ret
550
551 def CreateAnalyzer(self):
552 use_gdb = common.IsMac()
553 return memcheck_analyze.MemcheckAnalyzer(self._source_dir,
554 self._options.show_all_leaks,
555 use_gdb=use_gdb)
556
557 def Analyze(self, check_sanity=False):
558 ret = self.GetAnalyzeResults(check_sanity)
559
560 if ret != 0:
561 logging.info("Please see http://dev.chromium.org/developers/how-tos/"
562 "using-valgrind for the info on Memcheck/Valgrind")
563 return ret
564
565
566 class DrMemory(BaseTool):
567 """Dr.Memory
568 Dynamic memory error detector for Windows.
569
570 http://dev.chromium.org/developers/how-tos/using-drmemory
571 It is not very mature at the moment, some things might not work properly.
572 """
573
574 def __init__(self, full_mode, pattern_mode):
575 super(DrMemory, self).__init__()
576 self.full_mode = full_mode
577 self.pattern_mode = pattern_mode
578 self.RegisterOptionParserHook(DrMemory.ExtendOptionParser)
579
580 def ToolName(self):
581 return "drmemory"
582
583 def ExtendOptionParser(self, parser):
584 parser.add_option("", "--suppressions", default=[],
585 action="append",
586 help="path to a drmemory suppression file")
587 parser.add_option("", "--follow_python", action="store_true",
588 default=False, dest="follow_python",
589 help="Monitor python child processes. If off, neither "
590 "python children nor any children of python children "
591 "will be monitored.")
592 parser.add_option("", "--indirect", action="store_true",
593 default=False,
594 help="set BROWSER_WRAPPER rather than "
595 "running Dr. Memory directly on the harness")
596 parser.add_option("", "--indirect_webkit_layout", action="store_true",
597 default=False,
598 help="set --wrapper rather than running valgrind "
599 "directly.")
600 parser.add_option("", "--use_debug", action="store_true",
601 default=False, dest="use_debug",
602 help="Run Dr. Memory debug build")
603 parser.add_option("", "--trace_children", action="store_true",
604 default=True,
605 help="TODO: default value differs from Valgrind")
606 parser.add_option("", "--drmemory_ops",
607 help="Extra options passed to Dr. Memory")
608
609 def ToolCommand(self):
610 """Get the tool command to run."""
611 # WINHEAP is what Dr. Memory supports as there are issues w/ both
612 # jemalloc (https://github.com/DynamoRIO/drmemory/issues/320) and
613 # tcmalloc (https://github.com/DynamoRIO/drmemory/issues/314)
614 add_env = {
615 "CHROME_ALLOCATOR" : "WINHEAP",
616 "JSIMD_FORCEMMX" : "1", # https://github.com/DynamoRIO/drmemory/issues/ 540
617 }
618 for k,v in add_env.iteritems():
619 logging.info("export %s=%s", k, v)
620 os.putenv(k, v)
621
622 drmem_cmd = os.getenv("DRMEMORY_COMMAND")
623 if not drmem_cmd:
624 raise RuntimeError, "Please set DRMEMORY_COMMAND environment variable " \
625 "with the path to drmemory.exe"
626 proc = drmem_cmd.split(" ")
627
628 # By default, don't run python (this will exclude python's children as well)
629 # to reduce runtime. We're not really interested in spending time finding
630 # bugs in the python implementation.
631 # With file-based config we must update the file every time, and
632 # it will affect simultaneous drmem uses by this user. While file-based
633 # config has many advantages, here we may want this-instance-only
634 # (https://github.com/DynamoRIO/drmemory/issues/334).
635 drconfig_cmd = [ proc[0].replace("drmemory.exe", "drconfig.exe") ]
636 drconfig_cmd += ["-quiet"] # suppress errors about no 64-bit libs
637 run_drconfig = True
638 if self._options.follow_python:
639 logging.info("Following python children")
640 # -unreg fails if not already registered so query for that first
641 query_cmd = drconfig_cmd + ["-isreg", "python.exe"]
642 query_proc = subprocess.Popen(query_cmd, stdout=subprocess.PIPE,
643 shell=True)
644 (query_out, query_err) = query_proc.communicate()
645 if re.search("exe not registered", query_out):
646 run_drconfig = False # all set
647 else:
648 drconfig_cmd += ["-unreg", "python.exe"]
649 else:
650 logging.info("Excluding python children")
651 drconfig_cmd += ["-reg", "python.exe", "-norun"]
652 if run_drconfig:
653 drconfig_retcode = common.RunSubprocess(drconfig_cmd, self._timeout)
654 if drconfig_retcode:
655 logging.error("Configuring whether to follow python children failed " \
656 "with %d.", drconfig_retcode)
657 raise RuntimeError, "Configuring python children failed "
658
659 suppression_count = 0
660 supp_files = self._options.suppressions
661 if self.full_mode:
662 supp_files += [s.replace(".txt", "_full.txt") for s in supp_files]
663 for suppression_file in supp_files:
664 if os.path.exists(suppression_file):
665 suppression_count += 1
666 proc += ["-suppress", common.NormalizeWindowsPath(suppression_file)]
667
668 if not suppression_count:
669 logging.warning("WARNING: NOT USING SUPPRESSIONS!")
670
671 # Un-comment to dump Dr.Memory events on error
672 #proc += ["-dr_ops", "-dumpcore_mask", "-dr_ops", "0x8bff"]
673
674 # Un-comment and comment next line to debug Dr.Memory
675 #proc += ["-dr_ops", "-no_hide"]
676 #proc += ["-dr_ops", "-msgbox_mask", "-dr_ops", "15"]
677 #Proc += ["-dr_ops", "-stderr_mask", "-dr_ops", "15"]
678 # Ensure we see messages about Dr. Memory crashing!
679 proc += ["-dr_ops", "-stderr_mask", "-dr_ops", "12"]
680
681 if self._options.use_debug:
682 proc += ["-debug"]
683
684 proc += ["-logdir", common.NormalizeWindowsPath(self.log_dir)]
685
686 if self.log_parent_dir:
687 # gpu process on Windows Vista+ runs at Low Integrity and can only
688 # write to certain directories (http://crbug.com/119131)
689 symcache_dir = os.path.join(self.log_parent_dir, "drmemory.symcache")
690 elif self._options.build_dir:
691 # The other case is only possible with -t cmdline.
692 # Anyways, if we omit -symcache_dir the -logdir's value is used which
693 # should be fine.
694 symcache_dir = os.path.join(self._options.build_dir, "drmemory.symcache")
695 if symcache_dir:
696 if not os.path.exists(symcache_dir):
697 try:
698 os.mkdir(symcache_dir)
699 except OSError:
700 logging.warning("Can't create symcache dir?")
701 if os.path.exists(symcache_dir):
702 proc += ["-symcache_dir", common.NormalizeWindowsPath(symcache_dir)]
703
704 # Use -no_summary to suppress DrMemory's summary and init-time
705 # notifications. We generate our own with drmemory_analyze.py.
706 proc += ["-batch", "-no_summary"]
707
708 # Un-comment to disable interleaved output. Will also suppress error
709 # messages normally printed to stderr.
710 #proc += ["-quiet", "-no_results_to_stderr"]
711
712 proc += ["-callstack_max_frames", "40"]
713
714 # disable leak scan for now
715 proc += ["-no_count_leaks", "-no_leak_scan"]
716
717 # disable warnings about unaddressable prefetches
718 proc += ["-no_check_prefetch"]
719
720 # crbug.com/413215, no heap mismatch check for Windows release build binary
721 if common.IsWindows() and "Release" in self._options.build_dir:
722 proc += ["-no_check_delete_mismatch"]
723
724 # We are seeing false positive invalid heap args on 64-bit, so we are
725 # disabling the feature for now (xref
726 # https://github.com/DynamoRIO/drmemory/issues/1839).
727 if common.IsWindows() and "Release_x64" in self._options.build_dir:
728 proc += ["-no_check_heap_mismatch"]
729
730 # make callstacks easier to read
731 proc += ["-callstack_srcfile_prefix",
732 "build\\src,chromium\\src,crt_build\\self_x86"]
733 proc += ["-callstack_modname_hide",
734 "*drmemory*,chrome.dll"]
735
736 boring_callers = common.BoringCallers(mangled=False, use_re_wildcards=False)
737 # TODO(timurrrr): In fact, we want "starting from .." instead of "below .."
738 proc += ["-callstack_truncate_below", ",".join(boring_callers)]
739
740 # crbug.com/155839: extra Dr. Memory options passed via --drmemory_ops
741 if self._options.drmemory_ops:
742 proc.extend(self._options.drmemory_ops.split())
743
744 if self.pattern_mode:
745 proc += ["-pattern", "0xf1fd", "-no_count_leaks", "-redzone_size", "0x20"]
746 elif not self.full_mode:
747 proc += ["-light"]
748
749 proc += self._tool_flags
750
751 # Dr.Memory requires -- to separate tool flags from the executable name.
752 proc += ["--"]
753
754 if self._options.indirect or self._options.indirect_webkit_layout:
755 wrapper_path = os.path.join(self._source_dir,
756 "tools", "valgrind", "browser_wrapper_win.py")
757 wrapper = " ".join(["python", wrapper_path] + proc)
758 self.CreateBrowserWrapper(wrapper)
759 logging.info("browser wrapper = " + " ".join(proc))
760 if self._options.indirect_webkit_layout:
761 proc = self._args
762 # Layout tests want forward slashes.
763 wrapper = wrapper.replace('\\', '/')
764 proc += ["--wrapper", wrapper]
765 return proc
766 else:
767 proc = []
768
769 # Note that self._args begins with the name of the exe to be run.
770 self._args[0] = common.NormalizeWindowsPath(self._args[0])
771 proc += self._args
772 return proc
773
774 def CreateBrowserWrapper(self, command):
775 os.putenv("BROWSER_WRAPPER", command)
776
777 def Analyze(self, check_sanity=False):
778 # Use one analyzer for all the log files to avoid printing duplicate reports
779 #
780 # TODO(timurrrr): unify this with Valgrind and other tools when we have
781 # https://github.com/DynamoRIO/drmemory/issues/684
782 analyzer = drmemory_analyze.DrMemoryAnalyzer()
783
784 ret = 0
785 if not self._options.indirect and not self._options.indirect_webkit_layout:
786 filenames = glob.glob(self.log_dir + "/*/results.txt")
787
788 ret = analyzer.Report(filenames, None, check_sanity)
789 else:
790 testcases = glob.glob(self.log_dir + "/testcase.*.logs")
791 # If we have browser wrapper, the per-test logdirs are named as
792 # "testcase.wrapper_PID.name".
793 # Let's extract the list of wrapper_PIDs and name it ppids.
794 # NOTE: ppids may contain '_', i.e. they are not ints!
795 ppids = set([f.split(".")[-2] for f in testcases])
796
797 for ppid in ppids:
798 testcase_name = None
799 try:
800 f = open("%s/testcase.%s.name" % (self.log_dir, ppid))
801 testcase_name = f.read().strip()
802 f.close()
803 except IOError:
804 pass
805 print "====================================================="
806 print " Below is the report for drmemory wrapper PID=%s." % ppid
807 if testcase_name:
808 print " It was used while running the `%s` test." % testcase_name
809 else:
810 # TODO(timurrrr): hm, the PID line is suppressed on Windows...
811 print " You can find the corresponding test"
812 print " by searching the above log for 'PID=%s'" % ppid
813 sys.stdout.flush()
814 ppid_filenames = glob.glob("%s/testcase.%s.logs/*/results.txt" %
815 (self.log_dir, ppid))
816 ret |= analyzer.Report(ppid_filenames, testcase_name, False)
817 print "====================================================="
818 sys.stdout.flush()
819
820 logging.info("Please see http://dev.chromium.org/developers/how-tos/"
821 "using-drmemory for the info on Dr. Memory")
822 return ret
823
824
825 class ToolFactory:
826 def Create(self, tool_name):
827 if tool_name == "memcheck":
828 return Memcheck()
829 if tool_name == "drmemory" or tool_name == "drmemory_light":
830 # TODO(timurrrr): remove support for "drmemory" when buildbots are
831 # switched to drmemory_light OR make drmemory==drmemory_full the default
832 # mode when the tool is mature enough.
833 return DrMemory(False, False)
834 if tool_name == "drmemory_full":
835 return DrMemory(True, False)
836 if tool_name == "drmemory_pattern":
837 return DrMemory(False, True)
838 try:
839 platform_name = common.PlatformNames()[0]
840 except common.NotImplementedError:
841 platform_name = sys.platform + "(Unknown)"
842 raise RuntimeError, "Unknown tool (tool=%s, platform=%s)" % (tool_name,
843 platform_name)
844
845 def CreateTool(tool):
846 return ToolFactory().Create(tool)
OLDNEW
« no previous file with comments | « tools/valgrind/valgrind.sh ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698