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

Side by Side Diff: tools/drmemory/scripts/valgrind_test.py

Issue 1464453003: Enable Dr. Memory to run javascript, pixel, and corpus tests (Closed) Base URL: https://pdfium.googlesource.com/pdfium.git@master
Patch Set: PTAL Created 5 years, 1 month 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
OLDNEW
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Runs an exe through Valgrind and puts the intermediate files in a 5 """Runs an exe through Valgrind and puts the intermediate files in a
6 directory. 6 directory.
7 """ 7 """
8 8
9 import datetime 9 import datetime
10 import glob 10 import glob
(...skipping 212 matching lines...) Expand 10 before | Expand all | Expand 10 after
223 retcode = -1 223 retcode = -1
224 return retcode 224 return retcode
225 225
226 def Run(self, args, module, min_runtime_in_seconds=0): 226 def Run(self, args, module, min_runtime_in_seconds=0):
227 MODULES_TO_SANITY_CHECK = ["base"] 227 MODULES_TO_SANITY_CHECK = ["base"]
228 228
229 check_sanity = module in MODULES_TO_SANITY_CHECK 229 check_sanity = module in MODULES_TO_SANITY_CHECK
230 return self.Main(args, check_sanity, min_runtime_in_seconds) 230 return self.Main(args, check_sanity, min_runtime_in_seconds)
231 231
232 232
233 class ValgrindTool(BaseTool):
234 """Abstract class for running Valgrind tools.
235
236 Always subclass this and implement ToolSpecificFlags() and
237 ExtendOptionParser() for tool-specific stuff.
238 """
239 def __init__(self):
240 super(ValgrindTool, self).__init__()
241 self.RegisterOptionParserHook(ValgrindTool.ExtendOptionParser)
242
243 def UseXML(self):
244 # Override if tool prefers nonxml output
245 return True
246
247 def ExtendOptionParser(self, parser):
248 parser.add_option("", "--suppressions", default=[],
249 action="append",
250 help="path to a valgrind suppression file")
251 parser.add_option("", "--indirect", action="store_true",
252 default=False,
253 help="set BROWSER_WRAPPER rather than "
254 "running valgrind directly")
255 parser.add_option("", "--indirect_webkit_layout", action="store_true",
256 default=False,
257 help="set --wrapper rather than running Dr. Memory "
258 "directly.")
259 parser.add_option("", "--trace_children", action="store_true",
260 default=False,
261 help="also trace child processes")
262 parser.add_option("", "--num-callers",
263 dest="num_callers", default=30,
264 help="number of callers to show in stack traces")
265 parser.add_option("", "--generate_dsym", action="store_true",
266 default=False,
267 help="Generate .dSYM file on Mac if needed. Slow!")
268
269 def Setup(self, args):
270 if not BaseTool.Setup(self, args):
271 return False
272 if common.IsMac():
273 self.PrepareForTestMac()
274 return True
275
276 def PrepareForTestMac(self):
277 """Runs dsymutil if needed.
278
279 Valgrind for Mac OS X requires that debugging information be in a .dSYM
280 bundle generated by dsymutil. It is not currently able to chase DWARF
281 data into .o files like gdb does, so executables without .dSYM bundles or
282 with the Chromium-specific "fake_dsym" bundles generated by
283 build/mac/strip_save_dsym won't give source file and line number
284 information in valgrind.
285
286 This function will run dsymutil if the .dSYM bundle is missing or if
287 it looks like a fake_dsym. A non-fake dsym that already exists is assumed
288 to be up-to-date.
289 """
290 test_command = self._args[0]
291 dsym_bundle = self._args[0] + '.dSYM'
292 dsym_file = os.path.join(dsym_bundle, 'Contents', 'Resources', 'DWARF',
293 os.path.basename(test_command))
294 dsym_info_plist = os.path.join(dsym_bundle, 'Contents', 'Info.plist')
295
296 needs_dsymutil = True
297 saved_test_command = None
298
299 if os.path.exists(dsym_file) and os.path.exists(dsym_info_plist):
300 # Look for the special fake_dsym tag in dsym_info_plist.
301 dsym_info_plist_contents = open(dsym_info_plist).read()
302
303 if not re.search('^\s*<key>fake_dsym</key>$', dsym_info_plist_contents,
304 re.MULTILINE):
305 # fake_dsym is not set, this is a real .dSYM bundle produced by
306 # dsymutil. dsymutil does not need to be run again.
307 needs_dsymutil = False
308 else:
309 # fake_dsym is set. dsym_file is a copy of the original test_command
310 # before it was stripped. Copy it back to test_command so that
311 # dsymutil has unstripped input to work with. Move the stripped
312 # test_command out of the way, it will be restored when this is
313 # done.
314 saved_test_command = test_command + '.stripped'
315 os.rename(test_command, saved_test_command)
316 shutil.copyfile(dsym_file, test_command)
317 shutil.copymode(saved_test_command, test_command)
318
319 if needs_dsymutil:
320 if self._options.generate_dsym:
321 # Remove the .dSYM bundle if it exists.
322 shutil.rmtree(dsym_bundle, True)
323
324 dsymutil_command = ['dsymutil', test_command]
325
326 # dsymutil is crazy slow. Ideally we'd have a timeout here,
327 # but common.RunSubprocess' timeout is only checked
328 # after each line of output; dsymutil is silent
329 # until the end, and is then killed, which is silly.
330 common.RunSubprocess(dsymutil_command)
331
332 if saved_test_command:
333 os.rename(saved_test_command, test_command)
334 else:
335 logging.info("No real .dSYM for test_command. Line numbers will "
336 "not be shown. Either tell xcode to generate .dSYM "
337 "file, or use --generate_dsym option to this tool.")
338
339 def ToolCommand(self):
340 """Get the valgrind command to run."""
341 # Note that self._args begins with the exe to be run.
342 tool_name = self.ToolName()
343
344 # Construct the valgrind command.
345 if 'CHROME_VALGRIND' in os.environ:
346 path = os.path.join(os.environ['CHROME_VALGRIND'], "bin", "valgrind")
347 else:
348 path = "valgrind"
349 proc = [path, "--tool=%s" % tool_name]
350
351 proc += ["--num-callers=%i" % int(self._options.num_callers)]
352
353 if self._options.trace_children:
354 proc += ["--trace-children=yes"]
355 proc += ["--trace-children-skip='*dbus-daemon*'"]
356 proc += ["--trace-children-skip='*dbus-launch*'"]
357 proc += ["--trace-children-skip='*perl*'"]
358 proc += ["--trace-children-skip='*python*'"]
359 # This is really Python, but for some reason Valgrind follows it.
360 proc += ["--trace-children-skip='*lsb_release*'"]
361
362 proc += self.ToolSpecificFlags()
363 proc += self._tool_flags
364
365 suppression_count = 0
366 for suppression_file in self._options.suppressions:
367 if os.path.exists(suppression_file):
368 suppression_count += 1
369 proc += ["--suppressions=%s" % suppression_file]
370
371 if not suppression_count:
372 logging.warning("WARNING: NOT USING SUPPRESSIONS!")
373
374 logfilename = self.log_dir + ("/%s." % tool_name) + "%p"
375 if self.UseXML():
376 proc += ["--xml=yes", "--xml-file=" + logfilename]
377 else:
378 proc += ["--log-file=" + logfilename]
379
380 # The Valgrind command is constructed.
381
382 # Handle --indirect_webkit_layout separately.
383 if self._options.indirect_webkit_layout:
384 # Need to create the wrapper before modifying |proc|.
385 wrapper = self.CreateBrowserWrapper(proc, webkit=True)
386 proc = self._args
387 proc.append("--wrapper")
388 proc.append(wrapper)
389 return proc
390
391 if self._options.indirect:
392 wrapper = self.CreateBrowserWrapper(proc)
393 os.environ["BROWSER_WRAPPER"] = wrapper
394 logging.info('export BROWSER_WRAPPER=' + wrapper)
395 proc = []
396 proc += self._args
397 return proc
398
399 def ToolSpecificFlags(self):
400 raise NotImplementedError, "This method should be implemented " \
401 "in the tool-specific subclass"
402
403 def CreateBrowserWrapper(self, proc, webkit=False):
404 """The program being run invokes Python or something else that can't stand
405 to be valgrinded, and also invokes the Chrome browser. In this case, use a
406 magic wrapper to only valgrind the Chrome browser. Build the wrapper here.
407 Returns the path to the wrapper. It's up to the caller to use the wrapper
408 appropriately.
409 """
410 command = " ".join(proc)
411 # Add the PID of the browser wrapper to the logfile names so we can
412 # separate log files for different UI tests at the analyze stage.
413 command = command.replace("%p", "$$.%p")
414
415 (fd, indirect_fname) = tempfile.mkstemp(dir=self.log_dir,
416 prefix="browser_wrapper.",
417 text=True)
418 f = os.fdopen(fd, "w")
419 f.write('#!/bin/bash\n'
420 'echo "Started Valgrind wrapper for this test, PID=$$" >&2\n')
421
422 f.write('DIR=`dirname $0`\n'
423 'TESTNAME_FILE=$DIR/testcase.$$.name\n\n')
424
425 if webkit:
426 # Webkit layout_tests pass the URL as the first line of stdin.
427 f.write('tee $TESTNAME_FILE | %s "$@"\n' % command)
428 else:
429 # Try to get the test case name by looking at the program arguments.
430 # i.e. Chromium ui_tests used --test-name arg.
431 # TODO(timurrrr): This doesn't handle "--test-name Test.Name"
432 # TODO(timurrrr): ui_tests are dead. Where do we use the non-webkit
433 # wrapper now? browser_tests? What do they do?
434 f.write('for arg in $@\ndo\n'
435 ' if [[ "$arg" =~ --test-name=(.*) ]]\n then\n'
436 ' echo ${BASH_REMATCH[1]} >$TESTNAME_FILE\n'
437 ' fi\n'
438 'done\n\n'
439 '%s "$@"\n' % command)
440
441 f.close()
442 os.chmod(indirect_fname, stat.S_IRUSR|stat.S_IXUSR)
443 return indirect_fname
444
445 def CreateAnalyzer(self):
446 raise NotImplementedError, "This method should be implemented " \
447 "in the tool-specific subclass"
448
449 def GetAnalyzeResults(self, check_sanity=False):
450 # Glob all the files in the log directory
451 filenames = glob.glob(self.log_dir + "/" + self.ToolName() + ".*")
452
453 # If we have browser wrapper, the logfiles are named as
454 # "toolname.wrapper_PID.valgrind_PID".
455 # Let's extract the list of wrapper_PIDs and name it ppids
456 ppids = set([int(f.split(".")[-2]) \
457 for f in filenames if re.search("\.[0-9]+\.[0-9]+$", f)])
458
459 analyzer = self.CreateAnalyzer()
460 if len(ppids) == 0:
461 # Fast path - no browser wrapper was set.
462 return analyzer.Report(filenames, None, check_sanity)
463
464 ret = 0
465 for ppid in ppids:
466 testcase_name = None
467 try:
468 f = open(self.log_dir + ("/testcase.%d.name" % ppid))
469 testcase_name = f.read().strip()
470 f.close()
471 wk_layout_prefix="third_party/WebKit/LayoutTests/"
472 wk_prefix_at = testcase_name.rfind(wk_layout_prefix)
473 if wk_prefix_at != -1:
474 testcase_name = testcase_name[wk_prefix_at + len(wk_layout_prefix):]
475 except IOError:
476 pass
477 print "====================================================="
478 print " Below is the report for valgrind wrapper PID=%d." % ppid
479 if testcase_name:
480 print " It was used while running the `%s` test." % testcase_name
481 else:
482 print " You can find the corresponding test"
483 print " by searching the above log for 'PID=%d'" % ppid
484 sys.stdout.flush()
485
486 ppid_filenames = [f for f in filenames \
487 if re.search("\.%d\.[0-9]+$" % ppid, f)]
488 # check_sanity won't work with browser wrappers
489 assert check_sanity == False
490 ret |= analyzer.Report(ppid_filenames, testcase_name)
491 print "====================================================="
492 sys.stdout.flush()
493
494 if ret != 0:
495 print ""
496 print "The Valgrind reports are grouped by test names."
497 print "Each test has its PID printed in the log when the test was run"
498 print "and at the beginning of its Valgrind report."
499 print "Hint: you can search for the reports by Ctrl+F -> `=#`"
500 sys.stdout.flush()
501
502 return ret
503
504 class DrMemory(BaseTool): 233 class DrMemory(BaseTool):
505 """Dr.Memory 234 """Dr.Memory
506 Dynamic memory error detector for Windows. 235 Dynamic memory error detector for Windows.
507 236
508 http://dev.chromium.org/developers/how-tos/using-drmemory 237 http://dev.chromium.org/developers/how-tos/using-drmemory
509 It is not very mature at the moment, some things might not work properly. 238 It is not very mature at the moment, some things might not work properly.
510 """ 239 """
511 240
512 def __init__(self, full_mode, pattern_mode): 241 def __init__(self, full_mode, pattern_mode):
513 super(DrMemory, self).__init__() 242 super(DrMemory, self).__init__()
514 self.full_mode = full_mode 243 self.full_mode = full_mode
515 self.pattern_mode = pattern_mode 244 self.pattern_mode = pattern_mode
516 self.RegisterOptionParserHook(DrMemory.ExtendOptionParser) 245 self.RegisterOptionParserHook(DrMemory.ExtendOptionParser)
517 246
518 def ToolName(self): 247 def ToolName(self):
519 return "drmemory" 248 return "drmemory"
520 249
521 def ExtendOptionParser(self, parser): 250 def ExtendOptionParser(self, parser):
522 parser.add_option("", "--suppressions", default=[], 251 parser.add_option("", "--suppressions", default=[],
523 action="append", 252 action="append",
524 help="path to a drmemory suppression file") 253 help="path to a drmemory suppression file")
525 parser.add_option("", "--follow_python", action="store_true", 254 parser.add_option("", "--follow_python", action="store_true",
526 default=False, dest="follow_python", 255 default=False, dest="follow_python",
527 help="Monitor python child processes. If off, neither " 256 help="Monitor python child processes. If off, neither "
528 "python children nor any children of python children " 257 "python children nor any children of python children "
529 "will be monitored.") 258 "will be monitored.")
530 parser.add_option("", "--indirect", action="store_true", 259 parser.add_option("", "--indirect_pdfium_test", action="store_true",
531 default=False, 260 default=False,
532 help="set BROWSER_WRAPPER rather than " 261 help="set --wrapper rather than running Dr. Memory "
533 "running Dr. Memory directly on the harness")
534 parser.add_option("", "--indirect_webkit_layout", action="store_true",
535 default=False,
536 help="set --wrapper rather than running valgrind "
537 "directly.") 262 "directly.")
538 parser.add_option("", "--use_debug", action="store_true", 263 parser.add_option("", "--use_debug", action="store_true",
539 default=False, dest="use_debug", 264 default=False, dest="use_debug",
540 help="Run Dr. Memory debug build") 265 help="Run Dr. Memory debug build")
541 parser.add_option("", "--trace_children", action="store_true", 266 parser.add_option("", "--trace_children", action="store_true",
542 default=True, 267 default=True,
543 help="TODO: default value differs from Valgrind") 268 help="TODO: default value differs from Valgrind")
544 269
545 def ToolCommand(self): 270 def ToolCommand(self):
546 """Get the tool command to run.""" 271 """Get the tool command to run."""
(...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after
670 if self.pattern_mode: 395 if self.pattern_mode:
671 proc += ["-pattern", "0xf1fd", "-no_count_leaks", "-redzone_size", "0x20"] 396 proc += ["-pattern", "0xf1fd", "-no_count_leaks", "-redzone_size", "0x20"]
672 elif not self.full_mode: 397 elif not self.full_mode:
673 proc += ["-light"] 398 proc += ["-light"]
674 399
675 proc += self._tool_flags 400 proc += self._tool_flags
676 401
677 # Dr.Memory requires -- to separate tool flags from the executable name. 402 # Dr.Memory requires -- to separate tool flags from the executable name.
678 proc += ["--"] 403 proc += ["--"]
679 404
680 if self._options.indirect or self._options.indirect_webkit_layout: 405 if self._options.indirect_pdfium_test:
681 wrapper_path = os.path.join(self._source_dir, 406 wrapper = " ".join(proc)
682 "tools", "valgrind", "browser_wrapper_win.py") 407 logging.info("pdfium wrapper = " + wrapper)
683 wrapper = " ".join(["python", wrapper_path] + proc) 408 proc = self._args
684 self.CreateBrowserWrapper(wrapper) 409 proc += ["--wrapper", wrapper]
685 logging.info("browser wrapper = " + " ".join(proc)) 410 return proc
686 if self._options.indirect_webkit_layout:
687 proc = self._args
688 # Layout tests want forward slashes.
689 wrapper = wrapper.replace('\\', '/')
690 proc += ["--wrapper", wrapper]
691 return proc
692 else:
693 proc = []
694 411
695 # Note that self._args begins with the name of the exe to be run. 412 # Note that self._args begins with the name of the exe to be run.
696 self._args[0] = common.NormalizeWindowsPath(self._args[0]) 413 self._args[0] = common.NormalizeWindowsPath(self._args[0])
697 proc += self._args 414 proc += self._args
698 return proc 415 return proc
699 416
700 def CreateBrowserWrapper(self, command): 417 def CreateBrowserWrapper(self, command):
701 os.putenv("BROWSER_WRAPPER", command) 418 os.putenv("BROWSER_WRAPPER", command)
702 419
703 def Analyze(self, check_sanity=False): 420 def Analyze(self, check_sanity=False):
704 # Use one analyzer for all the log files to avoid printing duplicate reports 421 # Use one analyzer for all the log files to avoid printing duplicate reports
705 # 422 #
706 # TODO(timurrrr): unify this with Valgrind and other tools when we have 423 # TODO(timurrrr): unify this with Valgrind and other tools when we have
707 # https://github.com/DynamoRIO/drmemory/issues/684 424 # https://github.com/DynamoRIO/drmemory/issues/684
708 analyzer = drmemory_analyze.DrMemoryAnalyzer() 425 analyzer = drmemory_analyze.DrMemoryAnalyzer()
709 426
710 ret = 0 427 ret = 0
711 if not self._options.indirect and not self._options.indirect_webkit_layout: 428 if not self._options.indirect_pdfium_test:
712 filenames = glob.glob(self.log_dir + "/*/results.txt") 429 filenames = glob.glob(self.log_dir + "/*/results.txt")
713 430
714 ret = analyzer.Report(filenames, None, check_sanity) 431 ret = analyzer.Report(filenames, None, check_sanity)
715 else: 432 else:
716 testcases = glob.glob(self.log_dir + "/testcase.*.logs") 433 testcases = glob.glob(self.log_dir + "/testcase.*.logs")
717 # If we have browser wrapper, the per-test logdirs are named as 434 # If we have browser wrapper, the per-test logdirs are named as
718 # "testcase.wrapper_PID.name". 435 # "testcase.wrapper_PID.name".
719 # Let's extract the list of wrapper_PIDs and name it ppids. 436 # Let's extract the list of wrapper_PIDs and name it ppids.
720 # NOTE: ppids may contain '_', i.e. they are not ints! 437 # NOTE: ppids may contain '_', i.e. they are not ints!
721 ppids = set([f.split(".")[-2] for f in testcases]) 438 ppids = set([f.split(".")[-2] for f in testcases])
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
761 return DrMemory(False, True) 478 return DrMemory(False, True)
762 try: 479 try:
763 platform_name = common.PlatformNames()[0] 480 platform_name = common.PlatformNames()[0]
764 except common.NotImplementedError: 481 except common.NotImplementedError:
765 platform_name = sys.platform + "(Unknown)" 482 platform_name = sys.platform + "(Unknown)"
766 raise RuntimeError, "Unknown tool (tool=%s, platform=%s)" % (tool_name, 483 raise RuntimeError, "Unknown tool (tool=%s, platform=%s)" % (tool_name,
767 platform_name) 484 platform_name)
768 485
769 def CreateTool(tool): 486 def CreateTool(tool):
770 return ToolFactory().Create(tool) 487 return ToolFactory().Create(tool)
OLDNEW
« tools/drmemory/scripts/pdfium_tests.py ('K') | « tools/drmemory/scripts/pdfium_tests.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698