Bug 928397 - Enable xpcshell-test debugging on Windows platforms and default debugger detection. r=ted.mielczarek

This commit is contained in:
Alessio Placitelli 2014-07-02 13:52:00 +02:00
parent 09e9640c25
commit 3a4ff18bd7
6 changed files with 51 additions and 160 deletions

View File

@ -23,8 +23,6 @@ __all__ = [
"dumpLeakLog",
"isURL",
"processLeakLog",
"getDebuggerInfo",
"DEBUGGER_INFO",
"replaceBackSlashes",
'KeyValueParseError',
'parseKeyValue',
@ -48,54 +46,6 @@ def setAutomationLog(alt_logger):
global log
log = alt_logger
# Map of debugging programs to information about them, like default arguments
# and whether or not they are interactive.
DEBUGGER_INFO = {
# gdb requires that you supply the '--args' flag in order to pass arguments
# after the executable name to the executable.
"gdb": {
"interactive": True,
"args": "-q --args"
},
"cgdb": {
"interactive": True,
"args": "-q --args"
},
"lldb": {
"interactive": True,
"args": "--",
"requiresEscapedArgs": True
},
# Visual Studio Debugger Support
"devenv.exe": {
"interactive": True,
"args": "-debugexe"
},
# Visual C++ Express Debugger Support
"wdexpress.exe": {
"interactive": True,
"args": "-debugexe"
},
# valgrind doesn't explain much about leaks unless you set the
# '--leak-check=full' flag. But there are a lot of objects that are
# semi-deliberately leaked, so we set '--show-possibly-lost=no' to avoid
# uninteresting output from those objects. We set '--smc-check==all-non-file'
# and '--vex-iropt-register-updates=allregs-at-mem-access' so that valgrind
# deals properly with JIT'd JavaScript code.
"valgrind": {
"interactive": False,
"args": " ".join(["--leak-check=full",
"--show-possibly-lost=no",
"--smc-check=all-non-file",
"--vex-iropt-register-updates=allregs-at-mem-access"])
}
}
class ZipFileReader(object):
"""
Class to read zip files in Python 2.5 and later. Limited to only what we
@ -224,58 +174,6 @@ def addCommonOptions(parser, defaults={}):
help = "prevents the test harness from redirecting "
"stdout and stderr for interactive debuggers")
def getFullPath(directory, path):
"Get an absolute path relative to 'directory'."
return os.path.normpath(os.path.join(directory, os.path.expanduser(path)))
def searchPath(directory, path):
"Go one step beyond getFullPath and try the various folders in PATH"
# Try looking in the current working directory first.
newpath = getFullPath(directory, path)
if os.path.isfile(newpath):
return newpath
# At this point we have to fail if a directory was given (to prevent cases
# like './gdb' from matching '/usr/bin/./gdb').
if not os.path.dirname(path):
for dir in os.environ['PATH'].split(os.pathsep):
newpath = os.path.join(dir, path)
if os.path.isfile(newpath):
return newpath
return None
def getDebuggerInfo(directory, debugger, debuggerArgs, debuggerInteractive = False):
debuggerInfo = None
if debugger:
debuggerPath = searchPath(directory, debugger)
if not debuggerPath:
print "Error: Path %s doesn't exist." % debugger
sys.exit(1)
debuggerName = os.path.basename(debuggerPath).lower()
def getDebuggerInfo(type, default):
if debuggerName in DEBUGGER_INFO and type in DEBUGGER_INFO[debuggerName]:
return DEBUGGER_INFO[debuggerName][type]
return default
debuggerInfo = {
"path": debuggerPath,
"interactive" : getDebuggerInfo("interactive", False),
"args": getDebuggerInfo("args", "").split(),
"requiresEscapedArgs": getDebuggerInfo("requiresEscapedArgs", False)
}
if debuggerArgs:
debuggerInfo["args"] = debuggerArgs.split()
if debuggerInteractive:
debuggerInfo["interactive"] = debuggerInteractive
return debuggerInfo
def dumpLeakLog(leakLogFile, filter = False):
"""Process the leak log, without parsing it.

View File

@ -23,10 +23,10 @@ sys.path.insert(0, SCRIPT_DIRECTORY)
from automation import Automation
from automationutils import (
addCommonOptions,
getDebuggerInfo,
isURL,
processLeakLog
)
import mozdebug
import mozprofile
def categoriesToRegex(categoryList):
@ -320,7 +320,7 @@ class RefTest(object):
return int(any(t.retcode != 0 for t in threads))
def runSerialTests(self, testPath, options, cmdlineArgs = None):
debuggerInfo = getDebuggerInfo(self.oldcwd, options.debugger, options.debuggerArgs,
debuggerInfo = mozdebug.get_debugger_info(options.debugger, options.debuggerArgs,
options.debuggerInteractive);
profileDir = None

View File

@ -824,7 +824,7 @@ class DebugProgram(MachCommandBase):
@CommandArgument('+debugger', default=None, type=str,
help='Name of debugger to launch')
@CommandArgument('+debugparams', default=None, metavar='params', type=str,
help='Command-line arguments to pass to GDB or LLDB itself; split as the Bourne shell would.')
help='Command-line arguments to pass to the debugger itself; split as the Bourne shell would.')
# Bug 933807 introduced JS_DISABLE_SLOW_SCRIPT_SIGNALS to avoid clever
# segfaults induced by the slow-script-detecting logic for Ion/Odin JITted
# code. If we don't pass this, the user will need to periodically type
@ -833,26 +833,7 @@ class DebugProgram(MachCommandBase):
@CommandArgument('+slowscript', action='store_true',
help='Do not set the JS_DISABLE_SLOW_SCRIPT_SIGNALS env variable; when not set, recoverable but misleading SIGSEGV instances may occur in Ion/Odin JIT code')
def debug(self, params, remote, background, debugger, debugparams, slowscript):
import which
if debugger:
try:
debugger = which.which(debugger)
except Exception as e:
print("You don't have %s in your PATH" % (debugger))
print(e)
return 1
else:
try:
debugger = which.which('gdb')
except Exception:
try:
debugger = which.which('lldb')
except Exception as e:
print("You don't have gdb or lldb in your PATH")
print(e)
return 1
args = [debugger]
extra_env = { 'MOZ_CRASHREPORTER_DISABLE' : '1' }
# Parameters come from the CLI. We need to convert them before their use.
if debugparams:
import pymake.process
argv, badchar = pymake.process.clinetoargv(debugparams, os.getcwd())
@ -860,7 +841,22 @@ class DebugProgram(MachCommandBase):
print("The +debugparams you passed require a real shell to parse them.")
print("(We can't handle the %r character.)" % (badchar,))
return 1
args.extend(argv)
debugparams = argv;
import mozdebug
if not debugger:
# No debugger name was provided. Look for the default ones on current OS.
debugger = mozdebug.get_default_debugger_name(mozdebug.DebuggerSearch.KeepLooking)
self.debuggerInfo = mozdebug.get_debugger_info(debugger, debugparams)
# We could not find the information about the desired debugger.
if not self.debuggerInfo:
print("Could not find a suitable debugger in your PATH.")
return 1
extra_env = { 'MOZ_CRASHREPORTER_DISABLE' : '1' }
binpath = None
@ -872,17 +868,8 @@ class DebugProgram(MachCommandBase):
print(e)
return 1
# args added to separate the debugger and process arguments.
args_separator = {
'gdb': '--args',
'ddd': '--args',
'cgdb': '--args',
'lldb': '--'
}
debugger_name = os.path.basename(debugger)
if debugger_name in args_separator:
args.append(args_separator[debugger_name])
# Build the list of arguments to pass to run_process
args = [self.debuggerInfo.path] + self.debuggerInfo.args
args.append(binpath)
if not remote:

View File

@ -16,6 +16,7 @@ import ctypes
import glob
import json
import mozcrash
import mozdebug
import mozinfo
import mozprocess
import mozrunner
@ -34,7 +35,6 @@ import bisection
from automationutils import (
environment,
getDebuggerInfo,
isURL,
KeyValueParseError,
parseKeyValue,
@ -390,7 +390,7 @@ class WebSocketServer(object):
script = os.path.join(self._scriptdir, scriptPath)
cmd = [sys.executable, script]
if self.debuggerInfo and self.debuggerInfo['interactive']:
if self.debuggerInfo and self.debuggerInfo.interactive:
cmd += ['--interactive']
cmd += ['-p', str(self.port), '-w', self._scriptdir, '-l', \
os.path.join(self._scriptdir, "websock.log"), \
@ -1377,8 +1377,8 @@ class Mochitest(MochitestUtilsMixin):
interactive = False
debug_args = None
if debuggerInfo:
interactive = debuggerInfo['interactive']
debug_args = [debuggerInfo['path']] + debuggerInfo['args']
interactive = debuggerInfo.interactive
debug_args = [debuggerInfo.path] + debuggerInfo.args
# fix default timeout
if timeout == -1:
@ -1407,7 +1407,7 @@ class Mochitest(MochitestUtilsMixin):
# https://bugzilla.mozilla.org/show_bug.cgi?id=916512
args.append('-foreground')
if testUrl:
if debuggerInfo and debuggerInfo['requiresEscapedArgs']:
if debuggerInfo and debuggerInfo.requiresEscapedArgs:
testUrl = testUrl.replace("&", "\\&")
args.append(testUrl)
@ -1694,10 +1694,10 @@ class Mochitest(MochitestUtilsMixin):
# 'args': arguments to the debugger (list)
# TODO: use mozrunner.local.debugger_arguments:
# https://github.com/mozilla/mozbase/blob/master/mozrunner/mozrunner/local.py#L42
debuggerInfo = getDebuggerInfo(self.oldcwd,
options.debugger,
options.debuggerArgs,
options.debuggerInteractive)
debuggerInfo = mozdebug.get_debugger_info(options.debugger,
options.debuggerArgs,
options.debuggerInteractive)
if options.useTestMediaDevices:
devices = findTestMediaDevices(self.log)

View File

@ -146,15 +146,9 @@ class XPCShellRunner(MozbuildObject):
(manifest and len(manifest.test_paths())==1) or
verbose)
# We need to attach the '.exe' extension on Windows for the debugger to
# work properly.
xpcsExecutable = 'xpcshell'
if os.name == 'nt':
xpcsExecutable += '.exe'
args = {
'manifest': manifest,
'xpcshell': os.path.join(self.bindir, xpcsExecutable),
'xpcshell': self.get_binary_path('xpcshell'),
'mozInfo': os.path.join(self.topobjdir, 'mozinfo.json'),
'symbolsPath': os.path.join(self.distdir, 'crashreporter-symbols'),
'interactive': interactive,

View File

@ -7,6 +7,7 @@
import copy
import json
import math
import mozdebug
import os
import os.path
import random
@ -397,7 +398,7 @@ class XPCShellTestThread(Thread):
self.xpcsCmd.extend(['-f', os.path.join(self.testharnessdir, 'head.js')])
if self.debuggerInfo:
self.xpcsCmd = [self.debuggerInfo["path"]] + self.debuggerInfo["args"] + self.xpcsCmd
self.xpcsCmd = [self.debuggerInfo.path] + self.debuggerInfo.args + self.xpcsCmd
# Automation doesn't specify a pluginsPath and xpcshell defaults to
# $APPDIR/plugins. We do the same here so we can carry on with
@ -757,7 +758,6 @@ class XPCShellTestThread(Thread):
class XPCShellTests(object):
log = getGlobalLog()
oldcwd = os.getcwd()
def __init__(self, log=None):
""" Init logging and node status """
@ -904,7 +904,7 @@ class XPCShellTests(object):
pStdout = None
pStderr = None
else:
if (self.debuggerInfo and self.debuggerInfo["interactive"]):
if (self.debuggerInfo and self.debuggerInfo.interactive):
pStdout = None
pStderr = None
else:
@ -1190,8 +1190,11 @@ class XPCShellTests(object):
be printed always
|logfiles|, if set to False, indicates not to save output to log files.
Non-interactive only option.
|debuggerInfo|, if set, specifies the debugger and debugger arguments
that will be used to launch xpcshell.
|debugger|, if set, specifies the name of the debugger that will be used
to launch xpcshell.
|debuggerArgs|, if set, specifies arguments to use with the debugger.
|debuggerInteractive|, if set, allows the debugger to be run in interactive
mode.
|profileName|, if set, specifies the name of the application for the profile
directory if running only a subset of tests.
|mozInfo|, if set, specifies specifies build configuration information, either as a filename containing JSON, or a dict.
@ -1247,6 +1250,16 @@ class XPCShellTests(object):
if not testingModulesDir.endswith(os.path.sep):
testingModulesDir += os.path.sep
self.debuggerInfo = None
if debugger:
# We need a list of arguments, not a string, to feed into
# the debugger
if debuggerArgs:
debuggerArgs = debuggerArgs.split();
self.debuggerInfo = mozdebug.get_debugger_info(debugger, debuggerArgs, debuggerInteractive)
self.xpcshell = xpcshell
self.xrePath = xrePath
self.appPath = appPath
@ -1261,7 +1274,6 @@ class XPCShellTests(object):
self.on_message = on_message
self.totalChunks = totalChunks
self.thisChunk = thisChunk
self.debuggerInfo = getDebuggerInfo(self.oldcwd, debugger, debuggerArgs, debuggerInteractive)
self.profileName = profileName or "xpcshell"
self.mozInfo = mozInfo
self.testingModulesDir = testingModulesDir
@ -1362,7 +1374,7 @@ class XPCShellTests(object):
self.sequential = True
# If we have an interactive debugger, disable SIGINT entirely.
if self.debuggerInfo["interactive"]:
if self.debuggerInfo.interactive:
signal.signal(signal.SIGINT, lambda signum, frame: None)
# create a queue of all tests that will run