Bug 521457. Add debugger options to runreftest.py and runxpcshelltests.py. r=ted
authorJonathan Griffin <jgriffin@mozilla.com>
Mon, 19 Oct 2009 16:12:09 -0700
changeset 34020 0d4182d52170af7e3e85a38484cb293bddd9d147
parent 34019 f55bc23654b930fa9a9cedd7b4a0caf349ebc4a0
child 34021 af3e572a14f2020858b9ec98ccceb8ab5f93dcc0
push idunknown
push userunknown
push dateunknown
reviewersted
bugs521457
milestone1.9.3a1pre
Bug 521457. Add debugger options to runreftest.py and runxpcshelltests.py. r=ted
build/automationutils.py
layout/tools/reftest/runreftest.py
testing/mochitest/runtests.py.in
testing/xpcshell/runxpcshelltests.py
--- a/build/automationutils.py
+++ b/build/automationutils.py
@@ -39,32 +39,63 @@
 import glob, logging, os, subprocess, sys
 import re
 
 __all__ = [
   "addCommonOptions",
   "checkForCrashes",
   "dumpLeakLog",
   "processLeakLog",
+  "getDebuggerInfo",
+  "DEBUGGER_INFO",
   ]
 
+# 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"
+  },
+
+  # valgrind doesn't explain much about leaks unless you set the
+  # '--leak-check=full' flag.
+  "valgrind": {
+    "interactive": False,
+    "args": "--leak-check=full"
+  }
+}
+
 log = logging.getLogger()
 
 def addCommonOptions(parser, defaults={}):
   parser.add_option("--xre-path",
                     action = "store", type = "string", dest = "xrePath",
                     # individual scripts will set a sane default
                     default = None,
                     help = "absolute path to directory containing XRE (probably xulrunner)")
   if 'SYMBOLS_PATH' not in defaults:
     defaults['SYMBOLS_PATH'] = None
   parser.add_option("--symbols-path",
                     action = "store", type = "string", dest = "symbolsPath",
                     default = defaults['SYMBOLS_PATH'],
                     help = "absolute path to directory containing breakpad symbols")
+  parser.add_option("--debugger",
+                    action = "store", dest = "debugger",
+                    help = "use the given debugger to launch the application")
+  parser.add_option("--debugger-args",
+                    action = "store", dest = "debuggerArgs",
+                    help = "pass the given args to the debugger _before_ "
+                           "the application on the command line")
+  parser.add_option("--debugger-interactive",
+                    action = "store_true", dest = "debuggerInteractive",
+                    help = "prevents the test harness from redirecting "
+                        "stdout and stderr for interactive debuggers")
 
 def checkForCrashes(dumpDir, symbolsPath, testName=None):
   stackwalkPath = os.environ.get('MINIDUMP_STACKWALK', None)
   # try to get the caller's filename if no test name is given
   if testName is None:
     try:
       testName = os.path.basename(sys._getframe(1).f_code.co_filename)
     except:
@@ -86,16 +117,67 @@ def checkForCrashes(dumpDir, symbolsPath
         print "MINIDUMP_STACKWALK not set, can't process dump."
     os.remove(d)
     extra = os.path.splitext(d)[0] + ".extra"
     if os.path.exists(extra):
       os.remove(extra)
     foundCrash = True
 
   return foundCrash
+  
+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.exists(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.exists(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()
+    }
+
+    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.
 
   Use this function if you want the raw log only.
   Use it preferably with the |XPCOM_MEM_LEAK_LOG| environment variable.
   """
 
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -40,17 +40,17 @@
 """
 Runs the reftest test harness.
 """
 
 import sys, shutil, os, os.path
 SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
 sys.path.append(SCRIPT_DIRECTORY)
 import automation
-from automationutils import addCommonOptions, processLeakLog
+from automationutils import *
 from optparse import OptionParser
 from tempfile import mkdtemp
 
 oldcwd = os.getcwd()
 os.chdir(SCRIPT_DIRECTORY)
 
 def getFullPath(path):
   "Get an absolute path relative to oldcwd."
@@ -122,16 +122,19 @@ Are you executing $objdir/_tests/reftest
     options.xrePath = os.path.dirname(options.app)
   else:
     # allow relative paths
     options.xrePath = getFullPath(options.xrePath)
 
   options.symbolsPath = getFullPath(options.symbolsPath)
   options.utilityPath = getFullPath(options.utilityPath)
 
+  debuggerInfo = getDebuggerInfo(oldcwd, options.debugger, options.debuggerArgs,
+     options.debuggerInteractive);
+
   profileDir = None
   try:
     profileDir = mkdtemp()
     createReftestProfile(options, profileDir)
     copyExtraFilesToProfile(options, profileDir)
 
     # browser environment
     browserEnv = automation.environment(xrePath = options.xrePath)
@@ -160,16 +163,17 @@ Are you executing $objdir/_tests/reftest
 
     # then again to actually run reftest
     automation.log.info("REFTEST INFO | runreftest.py | Running tests: start.\n")
     reftestlist = getFullPath(args[0])
     status = automation.runApp(None, browserEnv, options.app, profileDir,
                                ["-reftest", reftestlist],
                                utilityPath = options.utilityPath,
                                xrePath=options.xrePath,
+                               debuggerInfo=debuggerInfo,
                                symbolsPath=options.symbolsPath)
     processLeakLog(leakLogFile, options.leakThreshold)
     automation.log.info("\nREFTEST INFO | runreftest.py | Running tests: end.")
   finally:
     if profileDir:
       shutil.rmtree(profileDir)
   sys.exit(status)
 
--- a/testing/mochitest/runtests.py.in
+++ b/testing/mochitest/runtests.py.in
@@ -48,17 +48,17 @@ import os
 import os.path
 import sys
 import time
 import shutil
 from urllib import quote_plus as encodeURIComponent
 import urllib2
 import commands
 import automation
-from automationutils import addCommonOptions, processLeakLog
+from automationutils import *
 
 # Path to the test script on the server
 TEST_SERVER_HOST = "localhost:8888"
 TEST_PATH = "/tests/"
 CHROME_PATH = "/redirect.html";
 A11Y_PATH = "/redirect-a11y.html"
 TESTS_URL = "http://" + TEST_SERVER_HOST + TEST_PATH
 CHROMETESTS_URL = "http://" + TEST_SERVER_HOST + CHROME_PATH
@@ -82,34 +82,16 @@ else:
 oldcwd = os.getcwd()
 SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
 os.chdir(SCRIPT_DIRECTORY)
 
 PROFILE_DIRECTORY = os.path.abspath("./mochitesttestingprofile")
 
 LEAK_REPORT_FILE = os.path.join(PROFILE_DIRECTORY, "runtests_leaks.log")
 
-# 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"
-  },
-
-  # valgrind doesn't explain much about leaks unless you set the
-  # '--leak-check=full' flag.
-  "valgrind": {
-    "interactive": False,
-    "args": "--leak-check=full"
-  }
-}
-
 #######################
 # COMMANDLINE OPTIONS #
 #######################
 
 class MochitestOptions(optparse.OptionParser):
   """Parses Mochitest commandline options."""
   def __init__(self, **kwargs):
     optparse.OptionParser.__init__(self, **kwargs)
@@ -246,30 +228,16 @@ class MochitestOptions(optparse.OptionPa
                            "(requires a debug build to be effective)")
     defaults["fatalAssertions"] = False
 
     self.add_option("--extra-profile-file",
                     action = "append", dest = "extraProfileFiles",
                     help = "copy specified files/dirs to testing profile")
     defaults["extraProfileFiles"] = []
 
-    self.add_option("--debugger",
-                    action = "store", dest = "debugger",
-                    help = "use the given debugger to launch the application")
-
-    self.add_option("--debugger-args",
-                    action = "store", dest = "debuggerArgs",
-                    help = "pass the given args to the debugger _before_ "
-                           "the application on the command line")
-
-    self.add_option("--debugger-interactive",
-                    action = "store_true", dest = "debuggerInteractive",
-                    help = "prevents the test harness from redirecting stdout "
-                           "and stderr for interactive debuggers")
-
     # -h, --help are automatically handled by OptionParser
 
     self.set_defaults(**defaults)
 
     usage = """\
 Usage instructions for runtests.py.
 All arguments are optional.
 If --chrome is specified, chrome tests will be run instead of web content tests.
@@ -337,32 +305,16 @@ class MochitestServer:
       self._process.wait()
     except:
       self._process.kill()
 
 def getFullPath(path):
   "Get an absolute path relative to oldcwd."
   return os.path.normpath(os.path.join(oldcwd, os.path.expanduser(path)))
 
-def searchPath(path):
-  "Go one step beyond getFullPath and try the various folders in PATH"
-  # Try looking in the current working directory first.
-  newpath = getFullPath(path)
-  if os.path.exists(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.exists(newpath):
-        return newpath
-  return None
-
 #################
 # MAIN FUNCTION #
 #################
 
 def main():
   parser = MochitestOptions()
   options, args = parser.parse_args()
 
@@ -392,41 +344,18 @@ Error: Path %(app)s doesn't exist.
 Are you executing $objdir/_tests/testing/mochitest/runtests.py?"""
     print msg % {"app": options.app}
     sys.exit(1)
 
   options.utilityPath = getFullPath(options.utilityPath)
   options.certPath = getFullPath(options.certPath)
   options.symbolsPath = getFullPath(options.symbolsPath)
 
-  debuggerInfo = None
-
-  if options.debugger:
-    debuggerPath = searchPath(options.debugger)
-    if not debuggerPath:
-      print "Error: Path %s doesn't exist." % options.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()
-    }
-
-    if options.debuggerArgs:
-      debuggerInfo["args"] = options.debuggerArgs.split()
-    if options.debuggerInteractive:
-      debuggerInfo["interactive"] = options.debuggerInteractive
+  debuggerInfo = getDebuggerInfo(oldcwd, options.debugger, options.debuggerArgs,
+    options.debuggerInteractive);
 
   # browser environment
   browserEnv = automation.environment(xrePath = options.xrePath)
 
   # These variables are necessary for correct application startup; change
   # via the commandline at your own risk.
   browserEnv["XPCOM_DEBUG_BREAK"] = "stack"
 
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -38,24 +38,26 @@
 # ***** END LICENSE BLOCK ***** */
 
 import re, sys, os, os.path, logging, shutil
 from glob import glob
 from optparse import OptionParser
 from subprocess import Popen, PIPE, STDOUT
 from tempfile import mkdtemp
 
-from automationutils import addCommonOptions, checkForCrashes, dumpLeakLog
+from automationutils import *
 
 # Init logging
 log = logging.getLogger()
 handler = logging.StreamHandler(sys.stdout)
 log.setLevel(logging.INFO)
 log.addHandler(handler)
 
+oldcwd = os.getcwd()
+
 def readManifest(manifest):
   """Given a manifest file containing a list of test directories,
   return a list of absolute paths to the directories contained within."""
   manifestdir = os.path.dirname(manifest)
   testdirs = []
   try:
     f = open(manifest, "r")
     for line in f:
@@ -65,32 +67,35 @@ def readManifest(manifest):
         testdirs.append(path)
     f.close()
   except:
     pass # just eat exceptions
   return testdirs
 
 def runTests(xpcshell, xrePath=None, symbolsPath=None,
              manifest=None, testdirs=[], testPath=None,
-             interactive=False, logfiles=True):
+             interactive=False, logfiles=True,
+             debuggerInfo=None):
   """Run xpcshell tests.
 
   |xpcshell|, is the xpcshell executable to use to run the tests.
   |xrePath|, if provided, is the path to the XRE to use.
   |symbolsPath|, if provided is the path to a directory containing
     breakpad symbols for processing crashes in tests.
   |manifest|, if provided, is a file containing a list of
     test directories to run.
   |testdirs|, if provided, is a list of absolute paths of test directories.
     No-manifest only option.
   |testPath|, if provided, indicates a single path and/or test to run.
   |interactive|, if set to True, indicates to provide an xpcshell prompt
     instead of automatically executing the test.
   |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.
   """
 
   if not testdirs and not manifest:
     # nothing to test!
     print >>sys.stderr, "Error: No test dirs or test manifest specified!"
     return False
 
   passCount = 0
@@ -126,27 +131,34 @@ def runTests(xpcshell, xrePath=None, sym
   if interactive:
     xpcsRunArgs = [
       '-e', 'print("To start the test, type |_execute_test();|.");',
       '-i']
     pStdout = None
     pStderr = None
   else:
     xpcsRunArgs = ['-e', '_execute_test();']
-    if sys.platform == 'os2emx':
+    if (debuggerInfo and debuggerInfo["interactive"]):
       pStdout = None
+      pStderr = None
     else:
-      pStdout = PIPE
-    pStderr = STDOUT
+      if sys.platform == 'os2emx':
+        pStdout = None
+      else:
+        pStdout = PIPE
+      pStderr = STDOUT
 
   # <head.js> has to be loaded by xpchell: it can't load itself.
   xpcsCmd = [xpcshell, '-g', xrePath, '-j', '-s'] + \
             ['-e', 'const _HTTPD_JS_PATH = "%s";' % httpdJSPath,
              '-f', os.path.join(testharnessdir, 'head.js')]
 
+  if debuggerInfo:
+    xpcsCmd = [debuggerInfo["path"]] + debuggerInfo["args"] + xpcsCmd
+
   # |testPath| will be the optional path only, or |None|.
   # |singleFile| will be the optional test only, or |None|.
   singleFile = None
   if testPath:
     if testPath.endswith('.js'):
       # Split into path and file.
       if testPath.find('/') == -1:
         # Test only.
@@ -290,24 +302,28 @@ def main():
 
   if len(args) < 2 and options.manifest is None or \
      (len(args) < 1 and options.manifest is not None):
     print >>sys.stderr, """Usage: %s <path to xpcshell> <test dirs>
   or: %s --manifest=test.manifest <path to xpcshell>""" % (sys.argv[0],
                                                            sys.argv[0])
     sys.exit(1)
 
+  debuggerInfo = getDebuggerInfo(oldcwd, options.debugger, options.debuggerArgs,
+    options.debuggerInteractive);
+
   if options.interactive and not options.testPath:
     print >>sys.stderr, "Error: You must specify a test filename in interactive mode!"
     sys.exit(1)
 
   if not runTests(args[0],
                   xrePath=options.xrePath,
                   symbolsPath=options.symbolsPath,
                   manifest=options.manifest,
                   testdirs=args[1:],
                   testPath=options.testPath,
                   interactive=options.interactive,
-                  logfiles=options.logfiles):
+                  logfiles=options.logfiles,
+                  debuggerInfo=debuggerInfo):
     sys.exit(1)
 
 if __name__ == '__main__':
   main()