Backed out changeset deabf7bc6526 (bug 1313488)
authorSebastian Hengst <archaeopteryx@coole-files.de>
Wed, 09 Nov 2016 21:10:10 +0100
changeset 348639 f2311b5d668122ebaea4aa132bec5da88b23f9f1
parent 348638 3aa99c2ab46d92ca0f1480d36aed87b49c575efd
child 348640 2d76c0ab3a375e9e89094ea993755dac8a932f81
push id10298
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:33:03 +0000
treeherdermozilla-aurora@7e29173b1641 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1313488
milestone52.0a1
backs outdeabf7bc65262d5470bd24590393f5fd2c0f34fe
Backed out changeset deabf7bc6526 (bug 1313488)
testing/cppunittest.ini
xpcom/tests/TestDeadlockDetector.cpp
xpcom/tests/gtest/TestDeadlockDetector.cpp
xpcom/tests/gtest/moz.build
xpcom/tests/moz.build
--- a/testing/cppunittest.ini
+++ b/testing/cppunittest.ini
@@ -10,16 +10,18 @@
 skip-if = os != 'win'
 [TestCasting]
 [TestCeilingFloor]
 [TestCertDB]
 [TestCheckedInt]
 [TestCookie]
 [TestCountPopulation]
 [TestCountZeroes]
+[TestDeadlockDetector]
+skip-if = os == 'b2g' || (os == 'android' && debug) # Bug 1054249
 [TestDeadlockDetectorScalability]
 [TestDllInterceptor]
 skip-if = os != 'win'
 [TestEndian]
 [TestEnumeratedArray]
 [TestEnumSet]
 [TestEnumTypeTraits]
 [TestFastBernoulliTrial]
rename from xpcom/tests/gtest/TestDeadlockDetector.cpp
rename to xpcom/tests/TestDeadlockDetector.cpp
--- a/xpcom/tests/gtest/TestDeadlockDetector.cpp
+++ b/xpcom/tests/TestDeadlockDetector.cpp
@@ -1,113 +1,322 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: sw=4 ts=4 et :
  * 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/. */
 
 #include "mozilla/ArrayUtils.h"
 
-#include "prthread.h"
+#include "prenv.h"
+#include "prerror.h"
+#include "prio.h"
+#include "prproces.h"
 
-#include "nsTArray.h"
 #include "nsMemory.h"
 
 #include "mozilla/CondVar.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/Mutex.h"
 
-#include "gtest/gtest.h"
+#include "TestHarness.h"
 
 using namespace mozilla;
 
 static PRThread*
 spawn(void (*run)(void*), void* arg)
 {
     return PR_CreateThread(PR_SYSTEM_THREAD,
                            run,
                            arg,
                            PR_PRIORITY_NORMAL,
                            PR_GLOBAL_THREAD,
                            PR_JOINABLE_THREAD,
                            0);
 }
 
-// This global variable is defined in toolkit/xre/nsSigHandlers.cpp.
-extern unsigned int _gdb_sleep_duration;
+#define PASS()                                  \
+    do {                                        \
+        passed(__FUNCTION__);                   \
+        return NS_OK;                           \
+    } while (0)
 
-/**
- * Simple test fixture that makes sure the gdb sleep setup in the
- * ah crap handler is bypassed during the death tests.
- */
-class DeadlockDetectorTest : public ::testing::Test
+#define FAIL(why)                               \
+    do {                                        \
+        fail("%s | %s - %s", __FILE__, __FUNCTION__, why); \
+        return NS_ERROR_FAILURE;                \
+    } while (0)
+
+//-----------------------------------------------------------------------------
+
+static const char* sPathToThisBinary;
+static const char* sAssertBehaviorEnv = "XPCOM_DEBUG_BREAK=abort";
+
+class Subprocess
 {
-protected:
-  void SetUp() final {
-    mOldSleepDuration = _gdb_sleep_duration;
-    _gdb_sleep_duration = 0;
-  }
+public:
+    // not available until process finishes
+    int32_t mExitCode;
+    nsCString mStdout;
+    nsCString mStderr;
+
+    explicit Subprocess(const char* aTestName) {
+        // set up stdio redirection
+        PRFileDesc* readStdin;  PRFileDesc* writeStdin;
+        PRFileDesc* readStdout; PRFileDesc* writeStdout;
+        PRFileDesc* readStderr; PRFileDesc* writeStderr;
+        PRProcessAttr* pattr = PR_NewProcessAttr();
+
+        NS_ASSERTION(pattr, "couldn't allocate process attrs");
+
+        NS_ASSERTION(PR_SUCCESS == PR_CreatePipe(&readStdin, &writeStdin),
+                     "couldn't create child stdin pipe");
+        NS_ASSERTION(PR_SUCCESS == PR_SetFDInheritable(readStdin, true),
+                     "couldn't set child stdin inheritable");
+        PR_ProcessAttrSetStdioRedirect(pattr, PR_StandardInput, readStdin);
+
+        NS_ASSERTION(PR_SUCCESS == PR_CreatePipe(&readStdout, &writeStdout),
+                     "couldn't create child stdout pipe");
+        NS_ASSERTION(PR_SUCCESS == PR_SetFDInheritable(writeStdout, true),
+                     "couldn't set child stdout inheritable");
+        PR_ProcessAttrSetStdioRedirect(pattr, PR_StandardOutput, writeStdout);
+
+        NS_ASSERTION(PR_SUCCESS == PR_CreatePipe(&readStderr, &writeStderr),
+                     "couldn't create child stderr pipe");
+        NS_ASSERTION(PR_SUCCESS == PR_SetFDInheritable(writeStderr, true),
+                     "couldn't set child stderr inheritable");
+        PR_ProcessAttrSetStdioRedirect(pattr, PR_StandardError, writeStderr);
+
+        // set up argv with test name to run
+        char* const newArgv[3] = {
+            strdup(sPathToThisBinary),
+            strdup(aTestName),
+            0
+        };
+
+        // make sure the child will abort if an assertion fails
+        NS_ASSERTION(PR_SUCCESS == PR_SetEnv(sAssertBehaviorEnv),
+                     "couldn't set XPCOM_DEBUG_BREAK env var");
+
+        PRProcess* proc;
+        NS_ASSERTION(proc = PR_CreateProcess(sPathToThisBinary,
+                                             newArgv,
+                                             0, // inherit environment
+                                             pattr),
+                     "couldn't create process");
+        PR_Close(readStdin);
+        PR_Close(writeStdout);
+        PR_Close(writeStderr);
+
+        mProc = proc;
+        mStdinfd = writeStdin;
+        mStdoutfd = readStdout;
+        mStderrfd = readStderr;
+
+        free(newArgv[0]);
+        free(newArgv[1]);
+        PR_DestroyProcessAttr(pattr);
+    }
+
+    void RunToCompletion(uint32_t aWaitMs)
+    {
+        PR_Close(mStdinfd);
 
-  void TearDown() final {
-    _gdb_sleep_duration = mOldSleepDuration;
-  }
+        PRPollDesc pollfds[2];
+        int32_t nfds;
+        bool stdoutOpen = true, stderrOpen = true;
+        char buf[4096];
+
+        PRIntervalTime now = PR_IntervalNow();
+        PRIntervalTime deadline = now + PR_MillisecondsToInterval(aWaitMs);
+
+        while ((stdoutOpen || stderrOpen) && now < deadline) {
+            nfds = 0;
+            if (stdoutOpen) {
+                pollfds[nfds].fd = mStdoutfd;
+                pollfds[nfds].in_flags = PR_POLL_READ;
+                pollfds[nfds].out_flags = 0;
+                ++nfds;
+            }
+            if (stderrOpen) {
+                pollfds[nfds].fd = mStderrfd;
+                pollfds[nfds].in_flags = PR_POLL_READ;
+                pollfds[nfds].out_flags = 0;
+                ++nfds;
+            }
+
+            int32_t rv = PR_Poll(pollfds, nfds, deadline - now);
+            NS_ASSERTION(0 <= rv, PR_ErrorToName(PR_GetError()));
+
+            if (0 == rv) {      // timeout
+                fputs("(timed out!)\n", stderr);
+                Finish(false); // abnormal
+                return;
+            }
+
+            for (int32_t i = 0; i < nfds; ++i) {
+                if (!pollfds[i].out_flags)
+                    continue;
+
+                bool isStdout = mStdoutfd == pollfds[i].fd;
+                int32_t len = 0;
+
+                if (PR_POLL_READ & pollfds[i].out_flags) {
+                    len = PR_Read(pollfds[i].fd, buf, sizeof(buf) - 1);
+                    NS_ASSERTION(0 <= len, PR_ErrorToName(PR_GetError()));
+                }
+                else if (!(PR_POLL_HUP & pollfds[i].out_flags)) {
+                    NS_ERROR(PR_ErrorToName(PR_GetError()));
+                }
+
+                if (0 < len) {
+                    buf[len] = '\0';
+                    if (isStdout)
+                        mStdout += buf;
+                    else
+                        mStderr += buf;
+                }
+                else if (isStdout) {
+                    stdoutOpen = false;
+                }
+                else {
+                    stderrOpen = false;
+                }
+            }
+
+            now = PR_IntervalNow();
+        }
+
+        if (stdoutOpen)
+            fputs("(stdout still open!)\n", stderr);
+        if (stderrOpen)
+            fputs("(stderr still open!)\n", stderr);
+        if (now > deadline)
+            fputs("(timed out!)\n", stderr);
+
+        Finish(!stdoutOpen && !stderrOpen && now <= deadline);
+    }
 
 private:
-  unsigned int mOldSleepDuration;
+    void Finish(bool normalExit) {
+        if (!normalExit) {
+            PR_KillProcess(mProc);
+            mExitCode = -1;
+            int32_t dummy;
+            PR_WaitProcess(mProc, &dummy);
+        }
+        else {
+            PR_WaitProcess(mProc, &mExitCode); // this had better not block ...
+        }
+
+        PR_Close(mStdoutfd);
+        PR_Close(mStderrfd);
+    }
+
+    PRProcess* mProc;
+    PRFileDesc* mStdinfd;         // writeable
+    PRFileDesc* mStdoutfd;        // readable
+    PRFileDesc* mStderrfd;        // readable
 };
 
 //-----------------------------------------------------------------------------
+// Harness for checking detector errors
+bool
+CheckForDeadlock(const char* test, const char* const* findTokens)
+{
+    Subprocess proc(test);
+    proc.RunToCompletion(5000);
+
+    if (0 == proc.mExitCode)
+        return false;
+
+    int32_t idx = 0;
+    for (const char* const* tp = findTokens; *tp; ++tp) {
+        const char* const token = *tp;
+#ifdef MOZILLA_INTERNAL_API
+        idx = proc.mStderr.Find(token, false, idx);
+#else
+        nsCString tokenCString(token);
+        idx = proc.mStderr.Find(tokenCString, idx);
+#endif
+        if (-1 == idx) {
+            printf("(missed token '%s' in output)\n", token);
+            puts("----------------------------------\n");
+            puts(proc.mStderr.get());
+            puts("----------------------------------\n");
+            return false;
+        }
+        idx += strlen(token);
+    }
+
+    return true;
+}
+
+//-----------------------------------------------------------------------------
 // Single-threaded sanity tests
 
 // Stupidest possible deadlock.
 int
 Sanity_Child()
 {
     mozilla::Mutex m1("dd.sanity.m1");
     m1.Lock();
     m1.Lock();
     return 0;                  // not reached
 }
 
-TEST_F(DeadlockDetectorTest, SanityDeathTest)
+nsresult
+Sanity()
 {
-    const char* const regex =
-        "###!!! ERROR: Potential deadlock detected.*"
-        "=== Cyclical dependency starts at.*--- Mutex : dd.sanity.m1.*"
-        "=== Cycle completed at.*--- Mutex : dd.sanity.m1.*"
-        "###!!! Deadlock may happen NOW!.*" // better catch these easy cases...
-        "###!!! ASSERTION: Potential deadlock detected.*";
-
-    ASSERT_DEATH(Sanity_Child(), regex);
+    const char* const tokens[] = {
+        "###!!! ERROR: Potential deadlock detected",
+        "=== Cyclical dependency starts at\n--- Mutex : dd.sanity.m1",
+        "=== Cycle completed at\n--- Mutex : dd.sanity.m1",
+        "###!!! Deadlock may happen NOW!", // better catch these easy cases...
+        "###!!! ASSERTION: Potential deadlock detected",
+        0
+    };
+    if (CheckForDeadlock("Sanity", tokens)) {
+        PASS();
+    } else {
+        FAIL("deadlock not detected");
+    }
 }
 
 // Slightly less stupid deadlock.
 int
 Sanity2_Child()
 {
     mozilla::Mutex m1("dd.sanity2.m1");
     mozilla::Mutex m2("dd.sanity2.m2");
     m1.Lock();
     m2.Lock();
     m1.Lock();
     return 0;                  // not reached
 }
 
-TEST_F(DeadlockDetectorTest, Sanity2DeathTest)
+nsresult
+Sanity2()
 {
-    const char* const regex =
-        "###!!! ERROR: Potential deadlock detected.*"
-        "=== Cyclical dependency starts at.*--- Mutex : dd.sanity2.m1.*"
-        "--- Next dependency:.*--- Mutex : dd.sanity2.m2.*"
-        "=== Cycle completed at.*--- Mutex : dd.sanity2.m1.*"
-        "###!!! Deadlock may happen NOW!.*" // better catch these easy cases...
-        "###!!! ASSERTION: Potential deadlock detected.*";
+    const char* const tokens[] = {
+        "###!!! ERROR: Potential deadlock detected",
+        "=== Cyclical dependency starts at\n--- Mutex : dd.sanity2.m1",
+        "--- Next dependency:\n--- Mutex : dd.sanity2.m2",
+        "=== Cycle completed at\n--- Mutex : dd.sanity2.m1",
+        "###!!! Deadlock may happen NOW!", // better catch these easy cases...
+        "###!!! ASSERTION: Potential deadlock detected",
+        0
+    };
+    if (CheckForDeadlock("Sanity2", tokens)) {
+        PASS();
+    } else {
+        FAIL("deadlock not detected");
+    }
+}
 
-    ASSERT_DEATH(Sanity2_Child(), regex);
-}
 
 int
 Sanity3_Child()
 {
     mozilla::Mutex m1("dd.sanity3.m1");
     mozilla::Mutex m2("dd.sanity3.m2");
     mozilla::Mutex m3("dd.sanity3.m3");
     mozilla::Mutex m4("dd.sanity3.m4");
@@ -121,175 +330,237 @@ Sanity3_Child()
     m2.Unlock();
     m1.Unlock();
 
     m4.Lock();
     m1.Lock();
     return 0;
 }
 
-TEST_F(DeadlockDetectorTest, Sanity3DeathTest)
+nsresult
+Sanity3()
 {
-    const char* const regex =
-        "###!!! ERROR: Potential deadlock detected.*"
-        "=== Cyclical dependency starts at.*--- Mutex : dd.sanity3.m1.*"
-        "--- Next dependency:.*--- Mutex : dd.sanity3.m2.*"
-        "--- Next dependency:.*--- Mutex : dd.sanity3.m3.*"
-        "--- Next dependency:.*--- Mutex : dd.sanity3.m4.*"
-        "=== Cycle completed at.*--- Mutex : dd.sanity3.m1.*"
-        "###!!! ASSERTION: Potential deadlock detected.*";
+    const char* const tokens[] = {
+        "###!!! ERROR: Potential deadlock detected",
+        "=== Cyclical dependency starts at\n--- Mutex : dd.sanity3.m1",
+        "--- Next dependency:\n--- Mutex : dd.sanity3.m2",
+        "--- Next dependency:\n--- Mutex : dd.sanity3.m3",
+        "--- Next dependency:\n--- Mutex : dd.sanity3.m4",
+        "=== Cycle completed at\n--- Mutex : dd.sanity3.m1",
+        "###!!! ASSERTION: Potential deadlock detected",
+        0
+    };
+    if (CheckForDeadlock("Sanity3", tokens)) {
+        PASS();
+    } else {
+        FAIL("deadlock not detected");
+    }
+}
 
-    ASSERT_DEATH(Sanity3_Child(), regex);
-}
 
 int
 Sanity4_Child()
 {
     mozilla::ReentrantMonitor m1("dd.sanity4.m1");
     mozilla::Mutex m2("dd.sanity4.m2");
     m1.Enter();
     m2.Lock();
     m1.Enter();
     return 0;
 }
 
-TEST_F(DeadlockDetectorTest, Sanity4DeathTest)
+nsresult
+Sanity4()
 {
-    const char* const regex =
-        "Re-entering ReentrantMonitor after acquiring other resources.*"
-        "###!!! ERROR: Potential deadlock detected.*"
-        "=== Cyclical dependency starts at.*--- ReentrantMonitor : dd.sanity4.m1.*"
-        "--- Next dependency:.*--- Mutex : dd.sanity4.m2.*"
-        "=== Cycle completed at.*--- ReentrantMonitor : dd.sanity4.m1.*"
-        "###!!! ASSERTION: Potential deadlock detected.*";
-    ASSERT_DEATH(Sanity4_Child(), regex);
+    const char* const tokens[] = {
+        "Re-entering ReentrantMonitor after acquiring other resources",
+        "###!!! ERROR: Potential deadlock detected",
+        "=== Cyclical dependency starts at\n--- ReentrantMonitor : dd.sanity4.m1",
+        "--- Next dependency:\n--- Mutex : dd.sanity4.m2",
+        "=== Cycle completed at\n--- ReentrantMonitor : dd.sanity4.m1",
+        "###!!! ASSERTION: Potential deadlock detected",
+        0
+    };
+    if (CheckForDeadlock("Sanity4", tokens)) {
+        PASS();
+    } else {
+        FAIL("deadlock not detected");
+    }
 }
 
 //-----------------------------------------------------------------------------
 // Multithreaded tests
 
-/**
- * Helper for passing state to threads in the multithread tests.
- */
-struct ThreadState
-{
-  /**
-   * Locks to use during the test. This is just a reference and is owned by
-   * the main test thread.
-   */
-  const nsTArray<mozilla::Mutex*>& locks;
-
-  /**
-   * Integer argument used to identify each thread.
-   */
-  int id;
-};
+mozilla::Mutex* ttM1;
+mozilla::Mutex* ttM2;
 
 static void
 TwoThreads_thread(void* arg)
 {
-    ThreadState* state = static_cast<ThreadState*>(arg);
-
-    mozilla::Mutex* ttM1 = state->locks[0];
-    mozilla::Mutex* ttM2 = state->locks[1];
-
-    if (state->id) {
+    int32_t m1First = NS_PTR_TO_INT32(arg);
+    if (m1First) {
         ttM1->Lock();
         ttM2->Lock();
         ttM2->Unlock();
         ttM1->Unlock();
     }
     else {
         ttM2->Lock();
         ttM1->Lock();
         ttM1->Unlock();
         ttM2->Unlock();
     }
 }
 
 int
 TwoThreads_Child()
 {
-    nsTArray<mozilla::Mutex*> locks = {
-      new mozilla::Mutex("dd.twothreads.m1"),
-      new mozilla::Mutex("dd.twothreads.m2")
-    };
+    ttM1 = new mozilla::Mutex("dd.twothreads.m1");
+    ttM2 = new mozilla::Mutex("dd.twothreads.m2");
+    if (!ttM1 || !ttM2)
+        NS_RUNTIMEABORT("couldn't allocate mutexes");
 
-    ThreadState state_1 {locks, 0};
-    PRThread* t1 = spawn(TwoThreads_thread, &state_1);
+    PRThread* t1 = spawn(TwoThreads_thread, (void*) 0);
     PR_JoinThread(t1);
 
-    ThreadState state_2 {locks, 1};
-    PRThread* t2 = spawn(TwoThreads_thread, &state_2);
+    PRThread* t2 = spawn(TwoThreads_thread, (void*) 1);
     PR_JoinThread(t2);
 
-    for (auto& lock : locks) {
-      delete lock;
-    }
-
     return 0;
 }
 
-TEST_F(DeadlockDetectorTest, TwoThreadsDeathTest)
+nsresult
+TwoThreads()
 {
-    const char* const regex =
-        "###!!! ERROR: Potential deadlock detected.*"
-        "=== Cyclical dependency starts at.*--- Mutex : dd.twothreads.m2.*"
-        "--- Next dependency:.*--- Mutex : dd.twothreads.m1.*"
-        "=== Cycle completed at.*--- Mutex : dd.twothreads.m2.*"
-        "###!!! ASSERTION: Potential deadlock detected.*";
+    const char* const tokens[] = {
+        "###!!! ERROR: Potential deadlock detected",
+        "=== Cyclical dependency starts at\n--- Mutex : dd.twothreads.m2",
+        "--- Next dependency:\n--- Mutex : dd.twothreads.m1",
+        "=== Cycle completed at\n--- Mutex : dd.twothreads.m2",
+        "###!!! ASSERTION: Potential deadlock detected",
+        0
+    };
 
-    ASSERT_DEATH(TwoThreads_Child(), regex);
+    if (CheckForDeadlock("TwoThreads", tokens)) {
+        PASS();
+    } else {
+        FAIL("deadlock not detected");
+    }
 }
 
+
+mozilla::Mutex* cndMs[4];
+const uint32_t K = 100000;
+
 static void
 ContentionNoDeadlock_thread(void* arg)
 {
-    const uint32_t K = 100000;
-
-    ThreadState* state = static_cast<ThreadState*>(arg);
-    int32_t starti = static_cast<int32_t>(state->id);
-    auto& cndMs = state->locks;
+    int32_t starti = NS_PTR_TO_INT32(arg);
 
     for (uint32_t k = 0; k < K; ++k) {
-        for (int32_t i = starti; i < (int32_t)cndMs.Length(); ++i)
+        for (int32_t i = starti; i < (int32_t) ArrayLength(cndMs); ++i)
             cndMs[i]->Lock();
         // comment out the next two lines for deadlocking fun!
-        for (int32_t i = cndMs.Length() - 1; i >= starti; --i)
+        for (int32_t i = ArrayLength(cndMs) - 1; i >= starti; --i)
             cndMs[i]->Unlock();
 
         starti = (starti + 1) % 3;
     }
 }
 
 int
 ContentionNoDeadlock_Child()
 {
-    const size_t kMutexCount = 4;
+    PRThread* threads[3];
 
-    PRThread* threads[3];
-    nsTArray<mozilla::Mutex*> locks;
-    ThreadState states[] = {
-      { locks, 0 },
-      { locks, 1 },
-      { locks, 2 }
-    };
-
-    for (uint32_t i = 0; i < kMutexCount; ++i)
-        locks.AppendElement(new mozilla::Mutex("dd.cnd.ms"));
+    for (uint32_t i = 0; i < ArrayLength(cndMs); ++i)
+        cndMs[i] = new mozilla::Mutex("dd.cnd.ms");
 
     for (int32_t i = 0; i < (int32_t) ArrayLength(threads); ++i)
-        threads[i] = spawn(ContentionNoDeadlock_thread, states + i);
+        threads[i] = spawn(ContentionNoDeadlock_thread, NS_INT32_TO_PTR(i));
 
     for (uint32_t i = 0; i < ArrayLength(threads); ++i)
         PR_JoinThread(threads[i]);
 
-    for (uint32_t i = 0; i < locks.Length(); ++i)
-        delete locks[i];
+    for (uint32_t i = 0; i < ArrayLength(cndMs); ++i)
+        delete cndMs[i];
 
     return 0;
 }
 
-TEST_F(DeadlockDetectorTest, ContentionNoDeadlock)
+nsresult
+ContentionNoDeadlock()
+{
+    const char * func = __func__;
+    Subprocess proc(func);
+    proc.RunToCompletion(60000);
+    if (0 != proc.mExitCode) {
+        printf("(expected 0 == return code, got %d)\n", proc.mExitCode);
+        puts("(output)\n----------------------------------\n");
+        puts(proc.mStdout.get());
+        puts("----------------------------------\n");
+        puts("(error output)\n----------------------------------\n");
+        puts(proc.mStderr.get());
+        puts("----------------------------------\n");
+
+        FAIL("deadlock");
+    }
+    PASS();
+}
+
+
+
+//-----------------------------------------------------------------------------
+
+int
+main(int argc, char** argv)
 {
-  // Just check that this test runs to completion.
-  ASSERT_EQ(ContentionNoDeadlock_Child(), 0);
+    if (1 < argc) {
+        // XXX can we run w/o scoped XPCOM?
+        const char* test = argv[1];
+        ScopedXPCOM xpcom(test);
+        if (xpcom.failed())
+            return 1;
+
+        // running in a spawned process.  call the specificed child function.
+        if (!strcmp("Sanity", test))
+            return Sanity_Child();
+        if (!strcmp("Sanity2", test))
+            return Sanity2_Child();
+        if (!strcmp("Sanity3", test))
+            return Sanity3_Child();
+        if (!strcmp("Sanity4", test))
+            return Sanity4_Child();
+
+        if (!strcmp("TwoThreads", test))
+            return TwoThreads_Child();
+        if (!strcmp("ContentionNoDeadlock", test))
+            return ContentionNoDeadlock_Child();
+
+        fail("%s | %s - unknown child test", __FILE__, __FUNCTION__);
+        return 2;
+    }
+
+    ScopedXPCOM xpcom("XPCOM deadlock detector correctness (" __FILE__ ")");
+    if (xpcom.failed())
+        return 1;
+
+    // in the first invocation of this process.  we will be the "driver".
+    int rv = 0;
+
+    sPathToThisBinary = argv[0];
+
+    if (NS_FAILED(Sanity()))
+        rv = 1;
+    if (NS_FAILED(Sanity2()))
+        rv = 1;
+    if (NS_FAILED(Sanity3()))
+        rv = 1;
+    if (NS_FAILED(Sanity4()))
+        rv = 1;
+
+    if (NS_FAILED(TwoThreads()))
+        rv = 1;
+    if (NS_FAILED(ContentionNoDeadlock()))
+        rv = 1;
+
+    return rv;
 }
--- a/xpcom/tests/gtest/moz.build
+++ b/xpcom/tests/gtest/moz.build
@@ -39,23 +39,16 @@ UNIFIED_SOURCES += [
     'TestThreadUtils.cpp',
     'TestTimers.cpp',
     'TestTimeStamp.cpp',
     'TestTokenizer.cpp',
     'TestUTF.cpp',
     'TestXPIDLString.cpp',
 ]
 
-if CONFIG['MOZ_DEBUG'] and CONFIG['OS_ARCH'] not in ('WINNT') and CONFIG['OS_TARGET'] != 'Android':
-    # FIXME bug 523392: TestDeadlockDetector doesn't like Windows
-    # Bug 1054249: Doesn't work on Android
-    UNIFIED_SOURCES += [
-        'TestDeadlockDetector.cpp',
-    ]
-
 # Compile TestAllocReplacement separately so Windows headers don't pollute
 # the global namespace for other files.
 SOURCES += [
     'TestAllocReplacement.cpp',
     'TestCOMPtr.cpp', # Redefines IFoo and IBar
     'TestHashtables.cpp', # Redefines IFoo
     'TestNsRefPtr.cpp', # Redefines Foo
     #'TestTArray2.cpp',
--- a/xpcom/tests/moz.build
+++ b/xpcom/tests/moz.build
@@ -51,16 +51,17 @@ XPCSHELL_TESTS_MANIFESTS += ['unit/xpcsh
 #CPP_UNIT_TESTS += [
 #    'TestStaticAtoms',
 #]
 
 if CONFIG['MOZ_DEBUG'] and CONFIG['OS_ARCH'] not in ('WINNT'):
     # FIXME bug 523392: TestDeadlockDetector doesn't like Windows
     # FIXME bug 523378: also fails on OS X
     GeckoCppUnitTests([
+        'TestDeadlockDetector',
         'TestDeadlockDetectorScalability',
     ])
 
 if CONFIG['COMPILE_ENVIRONMENT']:
     TEST_HARNESS_FILES.xpcshell.xpcom.tests.unit += [
         '!/dist/bin/components/xpcomtest.xpt',
     ]