bug 787176 - Add a Python wrapper script for running C++ unit tests. r=jmaher,waldo
authorTed Mielczarek <ted.mielczarek@gmail.com>
Thu, 30 Aug 2012 15:20:38 -0400
changeset 109745 73a42225bf227f73b9608686a69d21dcdf2f5541
parent 109744 85cc26def0c153c2ff173ff71d9750b8c9c3e236
child 109746 499af47f62ebf901d89477ab4aed6103b22b7665
push id16168
push usertmielczarek@mozilla.com
push dateTue, 09 Oct 2012 13:27:40 +0000
treeherdermozilla-inbound@73a42225bf22 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjmaher, waldo
bugs787176
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 787176 - Add a Python wrapper script for running C++ unit tests. r=jmaher,waldo
config/rules.mk
js/src/config/rules.mk
testing/runcppunittests.py
xpcom/tests/TestHarness.h
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -100,22 +100,18 @@ ifdef CPP_UNIT_TESTS
 # Compile the tests to $(DIST)/bin.  Make lots of niceties available by default
 # through TestHarness.h, by modifying the list of includes and the libs against
 # which stuff links.
 CPPSRCS += $(CPP_UNIT_TESTS)
 SIMPLE_PROGRAMS += $(CPP_UNIT_TESTS:.cpp=$(BIN_SUFFIX))
 INCLUDES += -I$(DIST)/include/testing
 LIBS += $(XPCOM_GLUE_LDOPTS) $(NSPR_LIBS) $(MOZ_JS_LIBS) $(if $(JS_SHARED_LIBRARY),,$(MOZ_ZLIB_LIBS))
 
-# ...and run them the usual way
 check::
-	@$(EXIT_ON_ERROR) \
-	  for f in $(subst .cpp,$(BIN_SUFFIX),$(CPP_UNIT_TESTS)); do \
-	    XPCOM_DEBUG_BREAK=stack-and-abort $(RUN_TEST_PROGRAM) $(DIST)/bin/$$f; \
-	  done
+	@$(PYTHON) $(topsrcdir)/testing/runcppunittests.py --xre-path=$(DIST)/bin --symbols-path=$(DIST)/crashreporter-symbols $(subst .cpp,$(BIN_SUFFIX),$(CPP_UNIT_TESTS))
 
 endif # CPP_UNIT_TESTS
 
 .PHONY: check
 
 endif # ENABLE_TESTS
 
 
--- a/js/src/config/rules.mk
+++ b/js/src/config/rules.mk
@@ -100,22 +100,18 @@ ifdef CPP_UNIT_TESTS
 # Compile the tests to $(DIST)/bin.  Make lots of niceties available by default
 # through TestHarness.h, by modifying the list of includes and the libs against
 # which stuff links.
 CPPSRCS += $(CPP_UNIT_TESTS)
 SIMPLE_PROGRAMS += $(CPP_UNIT_TESTS:.cpp=$(BIN_SUFFIX))
 INCLUDES += -I$(DIST)/include/testing
 LIBS += $(XPCOM_GLUE_LDOPTS) $(NSPR_LIBS) $(MOZ_JS_LIBS) $(if $(JS_SHARED_LIBRARY),,$(MOZ_ZLIB_LIBS))
 
-# ...and run them the usual way
 check::
-	@$(EXIT_ON_ERROR) \
-	  for f in $(subst .cpp,$(BIN_SUFFIX),$(CPP_UNIT_TESTS)); do \
-	    XPCOM_DEBUG_BREAK=stack-and-abort $(RUN_TEST_PROGRAM) $(DIST)/bin/$$f; \
-	  done
+	@$(PYTHON) $(topsrcdir)/testing/runcppunittests.py --xre-path=$(DIST)/bin --symbols-path=$(DIST)/crashreporter-symbols $(subst .cpp,$(BIN_SUFFIX),$(CPP_UNIT_TESTS))
 
 endif # CPP_UNIT_TESTS
 
 .PHONY: check
 
 endif # ENABLE_TESTS
 
 
new file mode 100644
--- /dev/null
+++ b/testing/runcppunittests.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import with_statement
+import sys, optparse, os, tempfile, shutil
+import mozprocess, mozinfo, mozlog, mozcrash
+from contextlib import contextmanager
+
+log = mozlog.getLogger('cppunittests')
+
+@contextmanager
+def TemporaryDirectory():
+    tempdir = tempfile.mkdtemp()
+    yield tempdir
+    shutil.rmtree(tempdir)
+
+def run_one_test(prog, env, symbols_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.
+
+    Return True if the program exits with a zero status, False otherwise.
+    """
+    basename = os.path.basename(prog)
+    log.info("Running test %s", basename)
+    with TemporaryDirectory() as tempdir:
+        proc = mozprocess.ProcessHandler([prog],
+                                         cwd=tempdir,
+                                         env=env)
+        proc.run()
+        timeout = 300
+        proc.processOutput(timeout=timeout)
+        if proc.timedOut:
+            log.testFail("%s | timed out after %d seconds",
+                         basename, timeout)
+            return False
+        proc.waitForFinish(timeout=timeout)
+        if proc.timedOut:
+            log.testFail("%s | timed out after %d seconds",
+                         basename, timeout)
+            return False
+        if mozcrash.check_for_crashes(tempdir, symbols_path,
+                                      test_name=basename):
+            log.testFail("%s | test crashed", basename)
+            return False
+        result = proc.proc.returncode == 0
+        if not result:
+            log.testFail("%s | test failed with return code %d",
+                         basename, proc.proc.returncode)
+        return result
+
+def run_tests(programs, xre_path, symbols_path=None):
+    """
+    Run a set of C++ unit test programs.
+
+    Arguments:
+    * programs: An iterable containing paths to test programs.
+    * xre_path: A path to a directory containing a XUL Runtime Environment.
+    * symbols_path: A path to a directory containing Breakpad-formatted
+                    symbol files for producing stack traces on crash.
+
+    Returns True if all test programs exited with a zero status, False
+    otherwise.
+    """
+    if not os.path.isdir(xre_path):
+        log.error("xre_path does not exist: %s", xre_path)
+        return False
+    #TODO: stick this in a module?
+    env = dict(os.environ)
+    pathvar = ""
+    if mozinfo.os == "linux":
+        pathvar = "LD_LIBRARY_PATH"
+    elif mozinfo.os == "mac":
+        pathvar = "DYLD_LIBRARY_PATH"
+    elif mozinfo.os == "win":
+        pathvar = "PATH"
+    if pathvar:
+        if pathvar in env:
+            env[pathvar] = "%s%s%s" % (xre_path, os.pathsep, env[pathvar])
+        else:
+            env[pathvar] = xre_path
+    env["MOZILLA_FIVE_HOME"] = xre_path
+    env["MOZ_XRE_DIR"] = xre_path
+    #TODO: switch this to just abort once all C++ unit tests have
+    # been fixed to enable crash reporting
+    env["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
+    env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
+    env["MOZ_CRASHREPORTER"] = "1"
+    result = True
+    for prog in programs:
+        single_result = run_one_test(prog, env, symbols_path)
+        result = result and single_result
+    return result
+
+if __name__ == '__main__':
+    parser = optparse.OptionParser()
+    parser.add_option("--xre-path",
+                      action = "store", type = "string", dest = "xre_path",
+                      default = None,
+                      help = "absolute path to directory containing XRE (probably xulrunner)")
+    parser.add_option("--symbols-path",
+                      action = "store", type = "string", dest = "symbols_path",
+                      default = None,
+                      help = "absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols")
+    options, args = parser.parse_args()
+    if not args:
+        print >>sys.stderr, """Usage: %s <test binary> [<test binary>...]""" % sys.argv[0]
+        sys.exit(1)
+    if not options.xre_path:
+        print >>sys.stderr, """Error: --xre-path is required"""
+        sys.exit(1)
+    progs = [os.path.abspath(p) for p in args]
+    options.xre_path = os.path.abspath(options.xre_path)
+    result = run_tests(progs, options.xre_path, options.symbols_path)
+    sys.exit(0 if result else 1)
--- a/xpcom/tests/TestHarness.h
+++ b/xpcom/tests/TestHarness.h
@@ -17,16 +17,17 @@
  * Including jsdbgapi.h may cause build break with --disable-shared-js
  * This is a workaround for bug 673616.
  */
 #define STATIC_JS_API
 #endif
 
 #include "mozilla/Util.h"
 
+#include "prenv.h"
 #include "nsComponentManagerUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsStringGlue.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
@@ -173,16 +174,34 @@ class ScopedXPCOM : public nsIDirectoryS
 
       rv = profD->CreateUnique(nsIFile::DIRECTORY_TYPE, 0755);
       NS_ENSURE_SUCCESS(rv, nullptr);
 
       mProfD = profD;
       return profD.forget();
     }
 
+    already_AddRefed<nsIFile> GetGREDirectory()
+    {
+      if (mGRED) {
+        nsCOMPtr<nsIFile> copy = mGRED;
+        return copy.forget();
+      }
+
+      char* env = PR_GetEnv("MOZ_XRE_DIR");
+      nsCOMPtr<nsIFile> greD;
+      if (env) {
+        NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), false,
+                        getter_AddRefs(greD));
+      }
+
+      mGRED = greD;
+      return greD.forget();
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     //// nsIDirectoryServiceProvider
 
     NS_IMETHODIMP GetFile(const char *aProperty, bool *_persistent,
                           nsIFile **_result)
     {
       // If we were supplied a directory service provider, ask it first.
       if (mDirSvcProvider &&
@@ -202,16 +221,25 @@ class ScopedXPCOM : public nsIDirectoryS
         nsresult rv = profD->Clone(getter_AddRefs(clone));
         NS_ENSURE_SUCCESS(rv, rv);
 
         *_persistent = true;
         clone.forget(_result);
         return NS_OK;
       }
 
+      if (0 == strcmp(aProperty, NS_GRE_DIR)) {
+        nsCOMPtr<nsIFile> greD = GetGREDirectory();
+        NS_ENSURE_TRUE(greD, NS_ERROR_FAILURE);
+
+        *_persistent = true;
+        greD.forget(_result);
+        return NS_OK;
+      }
+
       return NS_ERROR_FAILURE;
     }
 
     ////////////////////////////////////////////////////////////////////////////
     //// nsIDirectoryServiceProvider2
 
     NS_IMETHODIMP GetFiles(const char *aProperty, nsISimpleEnumerator **_enum)
     {
@@ -225,16 +253,17 @@ class ScopedXPCOM : public nsIDirectoryS
      return NS_ERROR_FAILURE;
    }
 
   private:
     const char* mTestName;
     nsIServiceManager* mServMgr;
     nsCOMPtr<nsIDirectoryServiceProvider> mDirSvcProvider;
     nsCOMPtr<nsIFile> mProfD;
+    nsCOMPtr<nsIFile> mGRED;
 };
 
 NS_IMPL_QUERY_INTERFACE2(
   ScopedXPCOM,
   nsIDirectoryServiceProvider,
   nsIDirectoryServiceProvider2
 )