Bug 1316309 - Use mozcrash.kill_and_get_minidump() in xpcshell instead of automation.py, r?ted draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Wed, 09 Nov 2016 10:32:46 -0500
changeset 437893 3ae0d39fdfcfff6afa60c233e10b3fe551d2e238
parent 437246 d38d06f85ef59c5dbb5d4a1a8d895957a78714de
child 536758 ba15949a766bb404e5e1be63b2315b50b5091967
push id35547
push userahalberstadt@mozilla.com
push dateFri, 11 Nov 2016 21:56:39 +0000
reviewersted
bugs1316309
milestone52.0a1
Bug 1316309 - Use mozcrash.kill_and_get_minidump() in xpcshell instead of automation.py, r?ted There are a number of things that were going wrong with XPCShell on windows, this patch address them: 1) We were erroring out in Automation.killAndGetStack(), both failing to produce a minidump and failing to kill the process. This patch fixes both these issues by using mozcrash instead. 2) Occasionally we were occasionally raising a psutil.NoSuchProcess error when attempting to kill the process after the test. This appears to be caused by a race condition, but is safe to ignore as if the process doesn't exist, then we don't need to kill it. 3) Spurious "Unable to remove directory" errors in cleanup. This uses mozfile.remove to remedy this. MozReview-Commit-ID: 1g5qO8fpM7R
testing/xpcshell/runxpcshelltests.py
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -37,17 +37,16 @@ from threading import (
 )
 
 try:
     import psutil
     HAVE_PSUTIL = True
 except Exception:
     HAVE_PSUTIL = False
 
-from automation import Automation
 from xpcshellcommandline import parser_desktop
 
 SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
 
 HARNESS_TIMEOUT = 5 * 60
 
 # benchmarking on tbpl revealed that this works best for now
 NUM_THREADS = int(cpu_count() * 4)
@@ -66,16 +65,17 @@ mozbase = os.path.realpath(os.path.join(
 if os.path.isdir(mozbase):
     for package in os.listdir(mozbase):
         sys.path.append(os.path.join(mozbase, package))
 
 from manifestparser import TestManifest
 from manifestparser.filters import chunk_by_slice, tags, pathprefix
 from mozlog import commandline
 import mozcrash
+import mozfile
 import mozinfo
 from mozrunner.utils import get_stack_fixer_function
 
 # --------------------------------------------------------------
 
 # TODO: perhaps this should be in a more generally shared location?
 # This regex matches all of the C0 and C1 control characters
 # (U+0000 through U+001F; U+007F; U+0080 through U+009F),
@@ -113,16 +113,17 @@ class XPCShellTestThread(Thread):
         self.daemon = True
 
         self.test_object = test_object
         self.cleanup_dir_list = cleanup_dir_list
         self.retry = retry
 
         self.appPath = kwargs.get('appPath')
         self.xrePath = kwargs.get('xrePath')
+        self.utility_path = kwargs.get('utility_path')
         self.testingModulesDir = kwargs.get('testingModulesDir')
         self.debuggerInfo = kwargs.get('debuggerInfo')
         self.jsDebuggerInfo = kwargs.get('jsDebuggerInfo')
         self.pluginsPath = kwargs.get('pluginsPath')
         self.httpdManifest = kwargs.get('httpdManifest')
         self.httpdJSPath = kwargs.get('httpdJSPath')
         self.headJSPath = kwargs.get('headJSPath')
         self.testharnessdir = kwargs.get('testharnessdir')
@@ -188,17 +189,17 @@ class XPCShellTestThread(Thread):
         """
         return proc.kill()
 
     def removeDir(self, dirname):
         """
           Simple wrapper to remove (recursively) a given directory.
           On a remote system, we need to overload this to work on the remote filesystem.
         """
-        shutil.rmtree(dirname)
+        mozfile.remove(dirname)
 
     def poll(self, proc):
         """
           Simple wrapper to check if a process has terminated.
           On a remote system, this is overloaded to handle remote process communication.
         """
         return proc.poll()
 
@@ -267,27 +268,31 @@ class XPCShellTestThread(Thread):
         self.log.info("%s | current directory: %r" % (name, testdir))
         # Show only those environment variables that are changed from
         # the ambient environment.
         changedEnv = (set("%s=%s" % i for i in self.env.iteritems())
                       - set("%s=%s" % i for i in os.environ.iteritems()))
         self.log.info("%s | environment: %s" % (name, list(changedEnv)))
 
     def killTimeout(self, proc):
-        Automation().killAndGetStackNoScreenshot(proc.pid,
-                                                 self.appPath,
-                                                 self.debuggerInfo)
+        mozcrash.kill_and_get_minidump(proc.pid, self.tempDir, utility_path=self.utility_path)
 
     def postCheck(self, proc):
         """Checks for a still-running test process, kills it and fails the test if found.
         We can sometimes get here before the process has terminated, which would
         cause removeDir() to fail - so check for the process and kill it if needed.
         """
         if proc and self.poll(proc) is None:
-            self.kill(proc)
+            if HAVE_PSUTIL:
+                try:
+                    self.kill(proc)
+                except psutil.NoSuchProcess:
+                    pass
+            else:
+                self.kill(proc)
             message = "%s | Process still running after test!" % self.test_object['id']
             if self.retry:
                 self.log.info(message)
                 return
 
             self.log.error(message)
             self.log_full_output()
             self.failCount = 1
@@ -1176,16 +1181,17 @@ class XPCShellTests(object):
         self.jsDebuggerInfo = None
         if jsDebugger:
             # A namedtuple let's us keep .port instead of ['port']
             JSDebuggerInfo = namedtuple('JSDebuggerInfo', ['port'])
             self.jsDebuggerInfo = JSDebuggerInfo(port=jsDebuggerPort)
 
         self.xpcshell = xpcshell
         self.xrePath = xrePath
+        self.utility_path = utility_path
         self.appPath = appPath
         self.symbolsPath = symbolsPath
         self.tempDir = os.path.normpath(tempDir or tempfile.gettempdir())
         self.manifest = manifest
         self.dump_tests = dump_tests
         self.interactive = interactive
         self.verbose = verbose
         self.keepGoing = keepGoing
@@ -1226,18 +1232,18 @@ class XPCShellTests(object):
             if isinstance(k, unicode):
                 k = k.encode('ascii')
             fixedInfo[k] = v
         self.mozInfo = fixedInfo
 
         mozinfo.update(self.mozInfo)
 
         self.stack_fixer_function = None
-        if utility_path and os.path.exists(utility_path):
-            self.stack_fixer_function = get_stack_fixer_function(utility_path, self.symbolsPath)
+        if self.utility_path and os.path.exists(self.utility_path):
+            self.stack_fixer_function = get_stack_fixer_function(self.utility_path, self.symbolsPath)
 
         # buildEnvironment() needs mozInfo, so we call it after mozInfo is initialized.
         self.buildEnvironment()
 
         # The appDirKey is a optional entry in either the default or individual test
         # sections that defines a relative application directory for test runs. If
         # defined we pass 'grePath/$appDirKey' for the -a parameter of the xpcshell
         # test harness.
@@ -1259,16 +1265,17 @@ class XPCShellTests(object):
             random.shuffle(self.alltests)
 
         self.cleanup_dir_list = []
         self.try_again_list = []
 
         kwargs = {
             'appPath': self.appPath,
             'xrePath': self.xrePath,
+            'utility_path': self.utility_path,
             'testingModulesDir': self.testingModulesDir,
             'debuggerInfo': self.debuggerInfo,
             'jsDebuggerInfo': self.jsDebuggerInfo,
             'pluginsPath': self.pluginsPath,
             'httpdManifest': self.httpdManifest,
             'httpdJSPath': self.httpdJSPath,
             'headJSPath': self.headJSPath,
             'tempDir': self.tempDir,