bug 483062 - figure out how to get crash stacks from xpcshell tests. r=bsmedberg
authorTed Mielczarek <ted.mielczarek@gmail.com>
Mon, 11 May 2009 15:54:39 -0400
changeset 28231 6461b3507d98608fd398992b0306de679420fb4b
parent 28230 ed38105c9c2adbf9215a24fface28969df9f566c
child 28232 a610e430054b98508ddbf5cf3a1f6ba2becc609f
push idunknown
push userunknown
push dateunknown
reviewersbsmedberg
bugs483062
milestone1.9.2a1pre
bug 483062 - figure out how to get crash stacks from xpcshell tests. r=bsmedberg
build/Makefile.in
build/automation-build.mk
build/automation.py.in
build/automationutils.py
build/pgo/Makefile.in
config/pythonpath.py
config/rules.mk
js/src/config/rules.mk
layout/tools/reftest/Makefile.in
layout/tools/reftest/runreftest.py
testing/mochitest/Makefile.in
testing/mochitest/runtests.py.in
testing/testsuite-targets.mk
testing/xpcshell/head.js
testing/xpcshell/runxpcshelltests.py
toolkit/crashreporter/test/unit/test_crashreporter.js
--- a/build/Makefile.in
+++ b/build/Makefile.in
@@ -69,16 +69,17 @@ include $(topsrcdir)/config/rules.mk
 # we install to _leaktest/
 TARGET_DEPTH = ..
 include $(srcdir)/automation-build.mk
 
 _LEAKTEST_DIR = $(DEPTH)/_leaktest
 
 _LEAKTEST_FILES =    \
 		automation.py \
+		automationutils.py \
 		leaktest.py \
 		bloatcycle.html \
 		$(topsrcdir)/build/pgo/server-locations.txt \
 		$(NULL)
 
 leaktest.py: leaktest.py.in
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py $^ > $@
 	chmod +x $@
--- a/build/automation-build.mk
+++ b/build/automation-build.mk
@@ -59,11 +59,16 @@ AUTOMATION_PPARGS += -DIS_TEST_BUILD=0
 endif
 
 ifeq ($(MOZ_DEBUG), 1)
 AUTOMATION_PPARGS += -DIS_DEBUG_BUILD=1
 else
 AUTOMATION_PPARGS += -DIS_DEBUG_BUILD=0
 endif
 
-automation.py: $(topsrcdir)/build/automation.py.in $(topsrcdir)/build/automation-build.mk
+automationutils.py:
+	$(INSTALL) $(topsrcdir)/build/automationutils.py .
+
+automation.py: $(topsrcdir)/build/automation.py.in $(topsrcdir)/build/automation-build.mk automationutils.py
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
 	$(AUTOMATION_PPARGS) $(DEFINES) $(ACDEFINES) $< > $@
+
+GARBAGE += automation.py automationutils.py
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -43,17 +43,18 @@ import itertools
 import logging
 import os
 import re
 import shutil
 import signal
 import subprocess
 import sys
 import threading
-import glob
+
+from automationutils import checkForCrashes
 
 """
 Runs the browser from a script, and provides useful utilities
 for setting up the browser environment.
 """
 
 SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
 
@@ -66,16 +67,19 @@ SCRIPT_DIR = os.path.abspath(os.path.rea
            "Process",
            "initializeProfile",
            "DIST_BIN",
            "DEFAULT_APP",
            "CERTS_SRC_DIR",
            "environment",
            "dumpLeakLog",
            "processLeakLog",
+           "IS_TEST_BUILD",
+           "IS_DEBUG_BUILD",
+           "SYMBOLS_PATH",
           ]
 
 # These are generated in mozilla/build/Makefile.in
 #expand DIST_BIN = __XPC_BIN_PATH__
 #expand IS_WIN32 = len("__WIN32__") != 0
 #expand IS_MAC = __IS_MAC__ != 0
 #ifdef IS_CYGWIN
 #expand IS_CYGWIN = __IS_CYGWIN__ == 1
@@ -120,17 +124,17 @@ class Process(subprocess.Popen):
     if IS_WIN32:
       import platform
       pid = "%i" % self.pid
       if platform.release() == "2000":
         # Windows 2000 needs 'kill.exe' from the 'Windows 2000 Resource Kit tools'. (See bug 475455.)
         try:
           subprocess.Popen(["kill", "-f", pid]).wait()
         except:
-          log.info("TEST-UNEXPECTED-FAIL | (automation.py) | Missing 'kill' utility to kill process with pid=%s. Kill it manually!", pid)
+          log.info("TEST-UNEXPECTED-FAIL | automation.py | Missing 'kill' utility to kill process with pid=%s. Kill it manually!", pid)
       else:
         # Windows XP and later.
         subprocess.Popen(["taskkill", "/F", "/PID", pid]).wait()
     else:
       os.kill(self.pid, signal.SIGKILL)
 
 
 #################
@@ -404,31 +408,16 @@ def environment(env = None, xrePath = DI
   elif IS_WIN32:
     env["PATH"] = env["PATH"] + ";" + ldLibraryPath
 
   env['MOZ_CRASHREPORTER_NO_REPORT'] = '1'
   env['MOZ_CRASHREPORTER'] = '1'
 
   return env
 
-def checkForCrashes(profileDir, symbolsPath):
-  stackwalkPath = os.environ.get('MINIDUMP_STACKWALK', None)
-
-  foundCrash = False
-  dumps = glob.glob(os.path.join(profileDir, 'minidumps', '*.dmp'))
-  for d in dumps:
-    log.info("TEST-UNEXPECTED-FAIL | (automation.py) | Browser crashed (minidump found)")
-    if symbolsPath and stackwalkPath:
-      nullfd = open(os.devnull, 'w')
-      # eat minidump_stackwalk errors
-      subprocess.call([stackwalkPath, d, symbolsPath], stderr=nullfd)
-      nullfd.close()
-    os.remove(d)
-    foundCrash = True
-  return foundCrash
 
 ###############
 # RUN THE APP #
 ###############
 
 def dumpLeakLog(leakLogFile, filter = False):
   """Process the leak log, without parsing it.
 
@@ -537,23 +526,23 @@ def runApp(testURL, env, app, profileDir
            xrePath = DIST_BIN, certPath = CERTS_SRC_DIR,
            debuggerInfo = None, symbolsPath = SYMBOLS_PATH):
   "Run the app, log the duration it took to execute, return the status code."
 
   if IS_TEST_BUILD and runSSLTunnel:
     # create certificate database for the profile
     certificateStatus = fillCertificateDB(profileDir, certPath, utilityPath, xrePath)
     if certificateStatus != 0:
-      log.info("TEST-UNEXPECTED FAIL | (automation.py) | Certificate integration failed")
+      log.info("TEST-UNEXPECTED FAIL | automation.py | Certificate integration failed")
       return certificateStatus
 
     # start ssltunnel to provide https:// URLs capability
     ssltunnel = os.path.join(utilityPath, "ssltunnel" + BIN_SUFFIX)
     ssltunnelProcess = Process([ssltunnel, os.path.join(profileDir, "ssltunnel.cfg")], env = environment(xrePath = xrePath))
-    log.info("INFO | (automation.py) | SSL tunnel pid: %d", ssltunnelProcess.pid)
+    log.info("INFO | automation.py | SSL tunnel pid: %d", ssltunnelProcess.pid)
 
   # now run with the profile we created
   cmd = app
   if IS_MAC and not IS_CAMINO and not cmd.endswith("-bin"):
     cmd += "-bin"
   cmd = os.path.abspath(cmd)
 
   args = []
@@ -583,30 +572,30 @@ def runApp(testURL, env, app, profileDir
 
   # Don't redirect stdout and stderr if an interactive debugger is attached
   if debuggerInfo and debuggerInfo["interactive"]:
     outputPipe = None
   else:
     outputPipe = subprocess.PIPE
 
   proc = Process([cmd] + args, env = environment(env), stdout = outputPipe, stderr = subprocess.STDOUT)
-  log.info("INFO | (automation.py) | Application pid: %d", proc.pid)
+  log.info("INFO | automation.py | Application pid: %d", proc.pid)
 
   if outputPipe is None:
     log.info("TEST-INFO: Not logging stdout or stderr due to debugger connection")
   else:
     line = proc.stdout.readline()
     while line != "":
       log.info(line.rstrip())
       line = proc.stdout.readline()
 
   status = proc.wait()
   if status != 0:
-    log.info("TEST-UNEXPECTED-FAIL | (automation.py) | Exited with code %d during test run", status)
-  log.info("INFO | (automation.py) | Application ran for: %s", str(datetime.now() - startTime))
+    log.info("TEST-UNEXPECTED-FAIL | automation.py | Exited with code %d during test run", status)
+  log.info("INFO | automation.py | Application ran for: %s", str(datetime.now() - startTime))
 
-  if checkForCrashes(profileDir, symbolsPath):
+  if checkForCrashes(os.path.join(profileDir, "minidumps"), symbolsPath):
     status = -1
 
   if IS_TEST_BUILD and runSSLTunnel:
     ssltunnelProcess.kill()
 
   return status
new file mode 100644
--- /dev/null
+++ b/build/automationutils.py
@@ -0,0 +1,46 @@
+import sys, glob, os, subprocess, logging
+
+__all__ = [
+    "addCommonOptions",
+    "checkForCrashes",
+    ]
+
+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")
+
+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:
+          testName = "unknown"
+
+  foundCrash = False
+  dumps = glob.glob(os.path.join(dumpDir, '*.dmp'))
+  for d in dumps:
+    log.info("TEST-UNEXPECTED-FAIL | %s | application crashed (minidump found)", testName)
+    if symbolsPath and stackwalkPath:
+      nullfd = open(os.devnull, 'w')
+      # eat minidump_stackwalk errors
+      subprocess.call([stackwalkPath, d, symbolsPath], stderr=nullfd)
+      nullfd.close()
+    os.remove(d)
+    extra = os.path.splitext(d)[0] + ".extra"
+    if os.path.exists(extra):
+        os.remove(extra)
+    foundCrash = True
+  return foundCrash
--- a/build/pgo/Makefile.in
+++ b/build/pgo/Makefile.in
@@ -54,30 +54,31 @@ include $(topsrcdir)/config/rules.mk
 # We install to _profile/pgo
 TARGET_DEPTH = ../..
 include $(topsrcdir)/build/automation-build.mk
 
 # Stuff to make a build with a profile
 
 _PGO_FILES = 	\
   automation.py \
+  automationutils.py \
   profileserver.py \
   genpgocert.py \
   index.html \
   server-locations.txt \
   favicon.ico \
   $(NULL)
 
 genpgocert.py: genpgocert.py.in
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
 	$(AUTOMATION_PPARGS) $(DEFINES) $(ACDEFINES) $^ > $@
 
 profileserver.py: profileserver.py.in
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py $^ > $@
 	chmod +x $@
 
-GARBAGE += automation.py profileserver.py genpgocert.py
+GARBAGE += profileserver.py genpgocert.py
 
 libs:: $(_PGO_FILES)
 	$(INSTALL) $^ $(_PROFILE_DIR)
 
 genservercert::
 	$(PYTHON) $(DEPTH)/_profile/pgo/genpgocert.py --gen-server
--- a/config/pythonpath.py
+++ b/config/pythonpath.py
@@ -32,9 +32,9 @@ while True:
         continue
 
     break
 
 sys.argv.pop(0)
 script = sys.argv[0]
 
 sys.path[0:0] = [os.path.dirname(script)] + paths
-execfile(script, {'__name__': '__main__'})
+execfile(script, {'__name__': '__main__', '__file__': script})
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -151,36 +151,42 @@ libs::
           $(testxpcobjdir)/all-test-dirs.list \
           $(addprefix $(MODULE)/,$(XPCSHELL_TESTS))
 
 testxpcsrcdir = $(topsrcdir)/testing/xpcshell
 
 # Execute all tests in the $(XPCSHELL_TESTS) directories.
 # See also testsuite-targets.mk 'xpcshell-tests' target for global execution.
 xpcshell-tests:
-	$(PYTHON) -u \
+	$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
+          -I$(topsrcdir)/build \
           $(testxpcsrcdir)/runxpcshelltests.py \
+          --symbols-path=$(DIST)/crashreporter-symbols \
           $(DIST)/bin/xpcshell \
           $(foreach dir,$(XPCSHELL_TESTS),$(testxpcobjdir)/$(MODULE)/$(dir))
 
 # Execute a single test, specified in $(SOLO_FILE), but don't automatically
 # start the test. Instead, present the xpcshell prompt so the user can
 # attach a debugger and then start the test.
 check-interactive:
-	$(PYTHON) -u \
+	$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
+          -I$(topsrcdir)/build \
           $(testxpcsrcdir)/runxpcshelltests.py \
+          --symbols-path=$(DIST)/crashreporter-symbols \
           --test=$(SOLO_FILE) \
           --interactive \
           $(DIST)/bin/xpcshell \
           $(foreach dir,$(XPCSHELL_TESTS),$(testxpcobjdir)/$(MODULE)/$(dir))
 
 # Execute a single test, specified in $(SOLO_FILE)
 check-one:
-	$(PYTHON) -u \
+	$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
+          -I$(topsrcdir)/build \
           $(testxpcsrcdir)/runxpcshelltests.py \
+          --symbols-path=$(DIST)/crashreporter-symbols \
           --test=$(SOLO_FILE) \
           $(DIST)/bin/xpcshell \
           $(foreach dir,$(XPCSHELL_TESTS),$(testxpcobjdir)/$(MODULE)/$(dir))
 
 endif # XPCSHELL_TESTS
 
 ifdef CPP_UNIT_TESTS
 
--- a/js/src/config/rules.mk
+++ b/js/src/config/rules.mk
@@ -151,36 +151,42 @@ libs::
           $(testxpcobjdir)/all-test-dirs.list \
           $(addprefix $(MODULE)/,$(XPCSHELL_TESTS))
 
 testxpcsrcdir = $(topsrcdir)/testing/xpcshell
 
 # Execute all tests in the $(XPCSHELL_TESTS) directories.
 # See also testsuite-targets.mk 'xpcshell-tests' target for global execution.
 xpcshell-tests:
-	$(PYTHON) -u \
+	$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
+          -I$(topsrcdir)/build \
           $(testxpcsrcdir)/runxpcshelltests.py \
+          --symbols-path=$(DIST)/crashreporter-symbols \
           $(DIST)/bin/xpcshell \
           $(foreach dir,$(XPCSHELL_TESTS),$(testxpcobjdir)/$(MODULE)/$(dir))
 
 # Execute a single test, specified in $(SOLO_FILE), but don't automatically
 # start the test. Instead, present the xpcshell prompt so the user can
 # attach a debugger and then start the test.
 check-interactive:
-	$(PYTHON) -u \
+	$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
+          -I$(topsrcdir)/build \
           $(testxpcsrcdir)/runxpcshelltests.py \
+          --symbols-path=$(DIST)/crashreporter-symbols \
           --test=$(SOLO_FILE) \
           --interactive \
           $(DIST)/bin/xpcshell \
           $(foreach dir,$(XPCSHELL_TESTS),$(testxpcobjdir)/$(MODULE)/$(dir))
 
 # Execute a single test, specified in $(SOLO_FILE)
 check-one:
-	$(PYTHON) -u \
+	$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
+          -I$(topsrcdir)/build \
           $(testxpcsrcdir)/runxpcshelltests.py \
+          --symbols-path=$(DIST)/crashreporter-symbols \
           --test=$(SOLO_FILE) \
           $(DIST)/bin/xpcshell \
           $(foreach dir,$(XPCSHELL_TESTS),$(testxpcobjdir)/$(MODULE)/$(dir))
 
 endif # XPCSHELL_TESTS
 
 ifdef CPP_UNIT_TESTS
 
--- a/layout/tools/reftest/Makefile.in
+++ b/layout/tools/reftest/Makefile.in
@@ -70,20 +70,19 @@ make-xpi:
 	+$(MAKE) -C $(CURDIR) libs XPI_NAME=reftest
 copy-harness: make-xpi
 libs:: copy-harness
 endif
 
 _HARNESS_FILES = \
   $(srcdir)/runreftest.py \
   automation.py \
+  automationutils.py \
   $(NULL)
 
-GARBAGE += automation.py
-
 $(_DEST_DIR):
 	$(NSINSTALL) -D $@
 
 $(_HARNESS_FILES): $(_DEST_DIR)
 
 # copy harness and the reftest extension bits to $(_DEST_DIR)
 copy-harness: $(_HARNESS_FILES)
 	$(INSTALL) $(_HARNESS_FILES) $(_DEST_DIR)
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -39,16 +39,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
 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."
@@ -70,32 +71,27 @@ def createReftestProfile(profileDir):
   os.mkdir(profileExtensionsPath)
   reftestExtensionPath = os.path.join(SCRIPT_DIRECTORY, "reftest")
   extFile = open(os.path.join(profileExtensionsPath, "reftest@mozilla.org"), "w")
   extFile.write(reftestExtensionPath)
   extFile.close()
 
 def main():
   parser = OptionParser()
+
+  # we want to pass down everything from automation.__all__
+  addCommonOptions(parser, defaults=dict(zip(automation.__all__, [getattr(automation, x) for x in automation.__all__])))
   parser.add_option("--appname",
                     action = "store", type = "string", dest = "app",
                     default = os.path.join(SCRIPT_DIRECTORY, automation.DEFAULT_APP),
                     help = "absolute path to application, overriding default")
-  parser.add_option("--xre-path",
-                    action = "store", type = "string", dest = "xrePath",
-                    default = None, # default is set below
-                    help = "absolute path to directory containing XRE (probably xulrunner)")
   parser.add_option("--extra-profile-file",
                     action = "append", dest = "extraProfileFiles",
                     default = [],
                     help = "copy specified files/dirs to testing profile")
-  parser.add_option("--symbols-path",
-                    action = "store", type = "string", dest = "symbolsPath",
-                    default = automation.SYMBOLS_PATH,
-                    help = "absolute path to directory containing breakpad symbols")
   parser.add_option("--leak-threshold",
                     action = "store", type = "int", dest = "leakThreshold",
                     default = 0,
                     help = "fail if the number of bytes leaked through "
                            "refcounted objects (or bytes in classes with "
                            "MOZ_COUNT_CTOR and MOZ_COUNT_DTOR) is greater "
                            "than the given number")
 
--- a/testing/mochitest/Makefile.in
+++ b/testing/mochitest/Makefile.in
@@ -55,16 +55,17 @@ include $(topsrcdir)/config/rules.mk
 # necessary for relative objdir paths.
 TARGET_DEPTH = ../../..
 include $(topsrcdir)/build/automation-build.mk
 
 # files that get copied into $objdir/_tests/
 _SERV_FILES = 	\
 		runtests.py \
 		automation.py \
+		automationutils.py \
 		gen_template.pl \
 		server.js \
 		harness-a11y.xul \
 		harness-overlay.xul \
 		harness.xul \
 		browser-test-overlay.xul \
 		browser-test.js \
 		browser-harness.xul \
@@ -78,17 +79,17 @@ include $(topsrcdir)/build/automation-bu
 
 
 _DEST_DIR = $(DEPTH)/_tests/$(relativesrcdir)
 
 runtests.py: runtests.py.in
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
 	$(DEFINES) $(ACDEFINES) $^ > $@
 
-GARBAGE += automation.py runtests.py
+GARBAGE += runtests.py
 
 libs:: $(_SERV_FILES)
 	$(INSTALL) $^ $(_DEST_DIR)
 
 # Binaries that don't get packaged with the build,
 # but that we need for the test harness
 TEST_HARNESS_BINS := \
   xpcshell$(BIN_SUFFIX) \
--- a/testing/mochitest/runtests.py.in
+++ b/testing/mochitest/runtests.py.in
@@ -47,16 +47,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
 
 # 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
@@ -105,42 +106,34 @@ DEBUGGER_INFO = {
 #######################
 
 class MochitestOptions(optparse.OptionParser):
   """Parses Mochitest commandline options."""
   def __init__(self, **kwargs):
     optparse.OptionParser.__init__(self, **kwargs)
     defaults = {}
 
+    # we want to pass down everything from automation.__all__
+    addCommonOptions(self, defaults=dict(zip(automation.__all__, [getattr(automation, x) for x in automation.__all__])))
+
     self.add_option("--close-when-done",
                     action = "store_true", dest = "closeWhenDone",
                     help = "close the application when tests are done running")
     defaults["closeWhenDone"] = False
 
     self.add_option("--appname",
                     action = "store", type = "string", dest = "app",
                     help = "absolute path to application, overriding default")
     defaults["app"] = os.path.join(SCRIPT_DIRECTORY, automation.DEFAULT_APP)
 
-    self.add_option("--xre-path",
-                    action = "store", type = "string", dest = "xrePath",
-                    help = "absolute path to directory containing XRE (probably xulrunner)")
-    # we'll default this to the directory of app below
-    defaults["xrePath"] = None
-
     self.add_option("--utility-path",
                     action = "store", type = "string", dest = "utilityPath",
                     help = "absolute path to directory containing utility programs (xpcshell, ssltunnel, certutil)")
     defaults["utilityPath"] = automation.DIST_BIN
 
-    self.add_option("--symbols-path",
-                    action = "store", type = "string", dest = "symbolsPath",
-                    help = "absolute path to directory containing breakpad symbols")
-    defaults["symbolsPath"] = automation.SYMBOLS_PATH
-
     self.add_option("--certificate-path",
                     action = "store", type = "string", dest = "certPath",
                     help = "absolute path to directory containing certificate store to use testing profile")
     defaults["certPath"] = automation.CERTS_SRC_DIR
 
     self.add_option("--log-file",
                     action = "store", type = "string",
                     dest = "logFile", metavar = "FILE",
--- a/testing/testsuite-targets.mk
+++ b/testing/testsuite-targets.mk
@@ -92,17 +92,18 @@ crashtest:
 	$(call RUN_REFTEST,$(topsrcdir)/testing/crashtest/crashtests.list)
 	$(CHECK_TEST_ERROR)
 
 
 # Execute all xpcshell tests in the directories listed in the manifest.
 # See also config/rules.mk 'xpcshell-tests' target for local execution.
 # Usage: |make [TEST_PATH=...] [EXTRA_TEST_ARGS=...] xpcshell-tests|.
 xpcshell-tests:
-	$(PYTHON) -u \
+	$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
+          -I$(topsrcdir)/build \
 	  $(topsrcdir)/testing/xpcshell/runxpcshelltests.py \
 	  --manifest=$(DEPTH)/_tests/xpcshell/all-test-dirs.list \
 	  $(TEST_PATH_ARG) $(EXTRA_TEST_ARGS) \
 	  $(DIST)/bin/xpcshell
 
 
 # Package up the tests and test harnesses
 include $(topsrcdir)/toolkit/mozapps/installer/package-name.mk
--- a/testing/xpcshell/head.js
+++ b/testing/xpcshell/head.js
@@ -51,16 +51,28 @@ var _tests_pending = 0;
 // Disable automatic network detection, so tests work correctly when
 // not connected to a network.
 let (ios = Components.classes["@mozilla.org/network/io-service;1"]
            .getService(Components.interfaces.nsIIOService2)) {
   ios.manageOfflineStatus = false;
   ios.offline = false;
 }
 
+// Enable crash reporting, if possible
+// We rely on the Python harness to set MOZ_CRASHREPORTER_NO_REPORT
+// and handle checking for minidumps.
+if ("@mozilla.org/toolkit/crash-reporter;1" in Components.classes) {
+  let (crashReporter =
+        Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
+        .getService(Components.interfaces.nsICrashReporter)) {
+    crashReporter.enabled = true;
+    crashReporter.minidumpPath = do_get_cwd();
+  }
+}
+
 function _TimerCallback(expr) {
   this._expr = expr;
 }
 _TimerCallback.prototype = {
   _expr: "",
   QueryInterface: function(iid) {
     if (iid.Equals(Components.interfaces.nsITimerCallback) ||
         iid.Equals(Components.interfaces.nsISupports))
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -31,22 +31,30 @@
 # use your version of this file under the terms of the MPL, indicate your
 # decision by deleting the provisions above and replace them with the notice
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK ***** */
 
-import sys, os, os.path
+import sys, os, os.path, logging
 import tempfile
 from glob import glob
 from optparse import OptionParser
 from subprocess import Popen, PIPE, STDOUT
 
+from automationutils import addCommonOptions, checkForCrashes
+
+# Init logging
+log = logging.getLogger()
+handler = logging.StreamHandler(sys.stdout)
+log.setLevel(logging.INFO)
+log.addHandler(handler)
+
 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:
@@ -55,40 +63,44 @@ def readManifest(manifest):
       if os.path.isdir(path):
         testdirs.append(path)
     f.close()
   except:
     pass # just eat exceptions
   return testdirs
 
 def runTests(xpcshell, testdirs=[], xrePath=None, testPath=None,
-             manifest=None, interactive=False):
+             manifest=None, interactive=False, symbolsPath=None):
   """Run the tests in |testdirs| using the |xpcshell| executable.
 
   |xrePath|, if provided, is the path to the XRE to use.
   |testPath|, if provided, indicates a single path and/or test to run.
   |manifest|, if provided, is a file containing a list of
     test directories to run.
   |interactive|, if set to True, indicates to provide an xpcshell prompt
     instead of automatically executing  the test.
+  |symbolsPath|, if provided is the path to a directory containing
+    breakpad symbols for processing crashes in tests.
   """
 
   if not testdirs and not manifest:
     # nothing to test!
     print >>sys.stderr, "Error: No test dirs or test manifest specified!"
     return False
 
   testharnessdir = os.path.dirname(os.path.abspath(__file__))
   xpcshell = os.path.abspath(xpcshell)
   # we assume that httpd.js lives in components/ relative to xpcshell
   httpdJSPath = os.path.join(os.path.dirname(xpcshell), "components", "httpd.js").replace("\\", "/");
 
   env = dict(os.environ)
   # Make assertions fatal
   env["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
+  # Don't launch the crash reporter client
+  env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
 
   # Enable leaks (only) detection to its own log file.
   # Each test will overwrite it.
   leakLogFile = os.path.join(tempfile.gettempdir(), "runxpcshelltests_leaks.log")
   env["XPCOM_MEM_LEAK_LOG"] = leakLogFile
 
   def processLeakLog(leakLogFile):
     """Process the leak log."""
@@ -196,16 +208,17 @@ def runTests(xpcshell, testdirs=[], xreP
         # not sure what else to do here...
         return True
 
       if proc.returncode != 0 or stdout.find("*** PASS") == -1:
         print """TEST-UNEXPECTED-FAIL | %s | test failed (with xpcshell return code: %d), see following log:
   >>>>>>>
   %s
   <<<<<<<""" % (test, proc.returncode, stdout)
+        checkForCrashes(testdir, symbolsPath, testName=test)
         success = False
       else:
         print "TEST-PASS | %s | all tests passed" % test
 
       leakReport = processLeakLog(leakLogFile)
 
       try:
         f = open(test + '.log', 'w')
@@ -221,19 +234,18 @@ def runTests(xpcshell, testdirs=[], xreP
       if os.path.exists(leakLogFile):
         os.remove(leakLogFile)
 
   return success
 
 def main():
   """Process command line arguments and call runTests() to do the real work."""
   parser = OptionParser()
-  parser.add_option("--xre-path",
-                    action="store", type="string", dest="xrePath", default=None,
-                    help="absolute path to directory containing XRE (probably xulrunner)")
+
+  addCommonOptions(parser)
   parser.add_option("--test-path",
                     action="store", type="string", dest="testPath",
                     default=None, help="single path and/or test filename to test")
   parser.add_option("--interactive",
                     action="store_true", dest="interactive", default=False,
                     help="don't automatically run tests, drop to an xpcshell prompt")
   parser.add_option("--manifest",
                     action="store", type="string", dest="manifest",
@@ -250,13 +262,14 @@ def main():
   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], testdirs=args[1:],
                   xrePath=options.xrePath,
                   testPath=options.testPath,
                   interactive=options.interactive,
-                  manifest=options.manifest):
+                  manifest=options.manifest,
+                  symbolsPath=options.symbolsPath):
     sys.exit(1)
 
 if __name__ == '__main__':
   main()
--- a/toolkit/crashreporter/test/unit/test_crashreporter.js
+++ b/toolkit/crashreporter/test/unit/test_crashreporter.js
@@ -5,17 +5,16 @@ function run_test()
     return;
   }
 
   var cr = Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
                      .getService(Components.interfaces.nsICrashReporter);
   do_check_neq(cr, null);
 
   // check that we can enable the crashreporter
-  do_check_false(cr.enabled);
   cr.enabled = true;
   do_check_true(cr.enabled);
   // ensure that double-enabling doesn't error
   cr.enabled = true;
 
   // check setting/getting serverURL
   var ios = Components.classes["@mozilla.org/network/io-service;1"]
                       .getService(Components.interfaces.nsIIOService);