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 114388 73a42225bf227f73b9608686a69d21dcdf2f5541
parent 114387 85cc26def0c153c2ff173ff71d9750b8c9c3e236
child 114389 499af47f62ebf901d89477ab4aed6103b22b7665
push idunknown
push userunknown
push dateunknown
reviewersjmaher, waldo
bugs787176
milestone19.0a1
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
 )