Bug 1055224 - Run gtest output through a stack fixer. r=ahal draft
authorChris Manchester <cmanchester@mozilla.com>
Thu, 14 Jan 2016 16:34:27 -0800
changeset 321876 f7922d5cb939c6d02049cd4d2d1a4ba43d1427d4
parent 321643 98877064a5db143fc85a23f6fb50280f43ff1393
child 512987 6353f98e03a8621e20c7bc7cdeade10455dfa1f7
push id9478
push usercmanchester@mozilla.com
push dateFri, 15 Jan 2016 00:34:34 +0000
reviewersahal
bugs1055224
milestone46.0a1
Bug 1055224 - Run gtest output through a stack fixer. r=ahal
testing/gtest/rungtests.py
testing/mozbase/mozprocess/mozprocess/processhandler.py
testing/mozharness/configs/unittests/linux_unittest.py
testing/mozharness/configs/unittests/mac_unittest.py
testing/mozharness/configs/unittests/win_unittest.py
testing/testsuite-targets.mk
--- a/testing/gtest/rungtests.py
+++ b/testing/gtest/rungtests.py
@@ -9,26 +9,28 @@ from __future__ import with_statement
 from optparse import OptionParser
 import os
 import sys
 
 import mozcrash
 import mozinfo
 import mozlog
 import mozprocess
+from mozrunner.utils import get_stack_fixer_function
 
 log = mozlog.unstructured.getLogger('gtest')
 
 class GTests(object):
     # Time (seconds) to wait for test process to complete
     TEST_PROC_TIMEOUT = 1200
     # Time (seconds) in which process will be killed if it produces no output.
     TEST_PROC_NO_OUTPUT_TIMEOUT = 300
 
-    def run_gtest(self, prog, xre_path, cwd, symbols_path=None):
+    def run_gtest(self, prog, xre_path, cwd, symbols_path=None,
+                  utility_path=None):
         """
         Run a single C++ unit test program.
 
         Arguments:
         * prog: The path to the test program to run.
         * env: The environment to use for running the program.
         * symbols_path: A path to a directory containing Breakpad-formatted
                         symbol files for producing stack traces on crash.
@@ -37,19 +39,28 @@ class GTests(object):
         """
         self.xre_path = xre_path
         env = self.build_environment()
         log.info("Running gtest")
 
         if cwd and not os.path.isdir(cwd):
             os.makedirs(cwd)
 
+        stream_output = mozprocess.StreamOutput(sys.stdout)
+        process_output = stream_output
+        if utility_path:
+            stack_fixer = get_stack_fixer_function(utility_path, symbols_path)
+            if stack_fixer:
+                process_output = lambda line: stream_output(stack_fixer(line))
+
+
         proc = mozprocess.ProcessHandler([prog, "-unittest"],
                                          cwd=cwd,
-                                         env=env)
+                                         env=env,
+                                         processOutputLine=process_output)
         #TODO: After bug 811320 is fixed, don't let .run() kill the process,
         # instead use a timeout in .wait() and then kill to get a stack.
         proc.run(timeout=GTests.TEST_PROC_TIMEOUT,
                  outputTimeout=GTests.TEST_PROC_NO_OUTPUT_TIMEOUT)
         proc.wait()
         if proc.timedOut:
             log.testFail("gtest | timed out after %d seconds", GTests.TEST_PROC_TIMEOUT)
             return False
@@ -106,17 +117,17 @@ class GTests(object):
         if pathvar:
             if pathvar in env:
                 env[pathvar] = "%s%s%s" % (self.xre_path, os.pathsep, env[pathvar])
             else:
                 env[pathvar] = self.xre_path
 
         # ASan specific environment stuff
         # mozinfo is not set up properly to detect if ASan is enabled, so just always set these.
-        if mozinfo.isLinux or mozinfo.isMac:
+        if mozinfo.info["asan"]:
             # Symbolizer support
             llvmsym = os.path.join(self.xre_path, "llvm-symbolizer")
             if os.path.isfile(llvmsym):
                 env["ASAN_SYMBOLIZER_PATH"] = llvmsym
                 log.info("gtest | ASan using symbolizer at %s", llvmsym)
             else:
                 # This should be |testFail| instead of |info|. See bug 1050891.
                 log.info("gtest | Failed to find ASan symbolizer at %s", llvmsym)
@@ -133,33 +144,53 @@ class gtestOptions(OptionParser):
         self.add_option("--xre-path",
                         dest="xre_path",
                         default=None,
                         help="absolute path to directory containing XRE (probably xulrunner)")
         self.add_option("--symbols-path",
                         dest="symbols_path",
                         default=None,
                         help="absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols")
+        self.add_option("--utility-path",
+                        dest="utility_path",
+                        default=None,
+                        help="path to a directory containing utility program binaries")
+
+def update_mozinfo():
+    """walk up directories to find mozinfo.json update the info"""
+    path = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
+    dirs = set()
+    while path != os.path.expanduser('~'):
+        if path in dirs:
+            break
+        dirs.add(path)
+        path = os.path.split(path)[0]
+    mozinfo.find_and_update_from_json(*dirs)
 
 def main():
     parser = gtestOptions()
     options, args = parser.parse_args()
     if not args:
         print >>sys.stderr, """Usage: %s <binary>""" % sys.argv[0]
         sys.exit(1)
     if not options.xre_path:
         print >>sys.stderr, """Error: --xre-path is required"""
         sys.exit(1)
+    if not options.utility_path:
+        print >>sys.stderr, """Warning: --utility-path is required to process assertion stacks"""
+
+    update_mozinfo()
     prog = os.path.abspath(args[0])
     options.xre_path = os.path.abspath(options.xre_path)
     tester = GTests()
     try:
         result = tester.run_gtest(prog, options.xre_path,
                                   options.cwd,
-                                  symbols_path=options.symbols_path)
+                                  symbols_path=options.symbols_path,
+                                  utility_path=options.utility_path)
     except Exception, e:
         log.error(str(e))
         result = False
     sys.exit(0 if result else 1)
 
 if __name__ == '__main__':
     main()
 
--- a/testing/mozbase/mozprocess/mozprocess/processhandler.py
+++ b/testing/mozbase/mozprocess/mozprocess/processhandler.py
@@ -8,17 +8,17 @@ import os
 import signal
 import subprocess
 import sys
 import threading
 import time
 import traceback
 from Queue import Queue, Empty
 from datetime import datetime
-__all__ = ['ProcessHandlerMixin', 'ProcessHandler']
+__all__ = ['ProcessHandlerMixin', 'ProcessHandler', 'StreamOutput']
 
 # Set the MOZPROCESS_DEBUG environment variable to 1 to see some debugging output
 MOZPROCESS_DEBUG = os.getenv("MOZPROCESS_DEBUG")
 
 # We dont use mozinfo because it is expensive to import, see bug 933558.
 isWin = os.name == "nt"
 isPosix = os.name == "posix" # includes MacOS X
 
--- a/testing/mozharness/configs/unittests/linux_unittest.py
+++ b/testing/mozharness/configs/unittests/linux_unittest.py
@@ -167,16 +167,17 @@ config = {
             "run_filename": "runxpcshelltests.py",
             "testsdir": "xpcshell"
         },
         "gtest": {
             "options": [
                 "--xre-path=%(abs_res_dir)s",
                 "--cwd=%(gtest_dir)s",
                 "--symbols-path=%(symbols_path)s",
+                "--utility-path=tests/bin",
                 "%(binary_path)s",
             ],
             "run_filename": "rungtests.py",
         },
     },
     # local mochi suites
     "all_mochitest_suites": {
         "plain": [],
--- a/testing/mozharness/configs/unittests/mac_unittest.py
+++ b/testing/mozharness/configs/unittests/mac_unittest.py
@@ -134,16 +134,17 @@ config = {
             "run_filename": "runxpcshelltests.py",
             "testsdir": "xpcshell"
         },
         "gtest": {
             "options": [
                 "--xre-path=%(abs_res_dir)s",
                 "--cwd=%(gtest_dir)s",
                 "--symbols-path=%(symbols_path)s",
+                "--utility-path=tests/bin",
                 "%(binary_path)s",
             ],
             "run_filename": "rungtests.py",
         },
     },
     # local mochi suites
     "all_mochitest_suites": {
         "plain": [],
--- a/testing/mozharness/configs/unittests/win_unittest.py
+++ b/testing/mozharness/configs/unittests/win_unittest.py
@@ -142,16 +142,17 @@ config = {
             "run_filename": "runxpcshelltests.py",
             "testsdir": "xpcshell"
         },
         "gtest": {
             "options": [
                 "--xre-path=%(abs_res_dir)s",
                 "--cwd=%(gtest_dir)s",
                 "--symbols-path=%(symbols_path)s",
+                "--utility-path=tests/bin",
                 "%(binary_path)s",
             ],
             "run_filename": "rungtests.py",
         },
     },
     # local mochi suites
     "all_mochitest_suites":
     {
--- a/testing/testsuite-targets.mk
+++ b/testing/testsuite-targets.mk
@@ -504,16 +504,17 @@ stage-jstests: make-stage-dir
 stage-gtest: make-stage-dir
 # FIXME: (bug 1200311) We should be generating the gtest xul as part of the build.
 	$(MAKE) -C $(DEPTH)/testing/gtest gtest
 	$(NSINSTALL) -D $(PKG_STAGE)/gtest/gtest_bin
 	cp -RL $(DIST)/bin/gtest $(PKG_STAGE)/gtest/gtest_bin
 	cp -RL $(DEPTH)/_tests/gtest $(PKG_STAGE)
 	cp $(topsrcdir)/testing/gtest/rungtests.py $(PKG_STAGE)/gtest
 	cp $(DIST)/bin/dependentlibs.list.gtest $(PKG_STAGE)/gtest
+	cp $(DEPTH)/mozinfo.json $(PKG_STAGE)/gtest
 
 stage-android: make-stage-dir
 ifdef MOZ_ENABLE_SZIP
 # Tinderbox scripts are not unzipping everything, so the file needs to be in a directory it unzips
 	$(NSINSTALL) $(DIST)/host/bin/szip $(PKG_STAGE)/bin/host
 endif
 	$(NSINSTALL) $(DEPTH)/build/mobile/sutagent/android/sutAgentAndroid.apk $(PKG_STAGE)/bin
 	$(NSINSTALL) $(DEPTH)/build/mobile/sutagent/android/watcher/Watcher.apk $(PKG_STAGE)/bin