Bug 1073312 - Test DMD on TBPL (Linux-only). r=glandium.
authorNicholas Nethercote <nnethercote@mozilla.com>
Sun, 28 Sep 2014 20:50:52 -0700
changeset 209740 b30e3a050567803de30476019332ec9bd6349023
parent 209739 b885a82dc02a088f2f478c20f568dbb22f72130e
child 209741 b820349b5b4666e3ed8c750a58b60e12e8c64ba2
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersglandium
bugs1073312
milestone35.0a1
Bug 1073312 - Test DMD on TBPL (Linux-only). r=glandium.
memory/replace/dmd/DMD.cpp
memory/replace/dmd/check_test_output.py
memory/replace/dmd/dmd.py
memory/replace/dmd/moz.build
memory/replace/dmd/test/full-heap-expected2.txt
memory/replace/dmd/test/full-heap-expected3.txt
memory/replace/dmd/test/full-heap-expected4.txt
memory/replace/dmd/test/full-reports-expected2.txt
memory/replace/dmd/test/full-reports-expected3.txt
memory/replace/dmd/test/full-reports-expected4.txt
memory/replace/dmd/test/test_dmd.js
memory/replace/dmd/test/xpcshell.ini
testing/mochitest/Makefile.in
testing/xpcshell/runxpcshelltests.py
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -1397,28 +1397,26 @@ Init(const malloc_table_t* aMallocTable)
 
   gMallocTable = aMallocTable;
 
   // DMD is controlled by the |DMD| environment variable.
   // - If it's unset or empty or "0", DMD doesn't run.
   // - Otherwise, the contents dictate DMD's behaviour.
 
   char* e = getenv("DMD");
-  StatusMsg("$DMD = '%s'\n", e);
 
   if (!e || strcmp(e, "") == 0 || strcmp(e, "0") == 0) {
-    StatusMsg("DMD is not enabled\n");
     return;
   }
 
+  StatusMsg("$DMD = '%s'\n", e);
+
   // Parse $DMD env var.
   gOptions = InfallibleAllocPolicy::new_<Options>(e);
 
-  StatusMsg("DMD is enabled\n");
-
 #ifdef XP_MACOSX
   // On Mac OS X we need to call StackWalkInitCriticalAddress() very early
   // (prior to the creation of any mutexes, apparently) otherwise we can get
   // hangs when getting stack traces (bug 821577).  But
   // StackWalkInitCriticalAddress() isn't exported from xpcom/, so instead we
   // just call NS_StackWalk, because that calls StackWalkInitCriticalAddress().
   // See the comment above StackWalkInitCriticalAddress() for more details.
   (void)NS_StackWalk(NopStackWalkCallback, /* skipFrames */ 0,
@@ -1440,29 +1438,37 @@ Init(const malloc_table_t* aMallocTable)
     gBlockTable = InfallibleAllocPolicy::new_<BlockTable>();
     gBlockTable->init(8192);
   }
 
   if (gOptions->IsTestMode()) {
     // Do all necessary allocations before setting gIsDMDRunning so those
     // allocations don't show up in our results.  Once gIsDMDRunning is set we
     // are intercepting malloc et al. in earnest.
+    //
+    // These files are written to $CWD. It would probably be better to write
+    // them to "TmpD" using the directory service, but that would require
+    // linking DMD with XPCOM.
     auto f1 = MakeUnique<FpWriteFunc>(OpenOutputFile("full1.json"));
     auto f2 = MakeUnique<FpWriteFunc>(OpenOutputFile("full2.json"));
     auto f3 = MakeUnique<FpWriteFunc>(OpenOutputFile("full3.json"));
     auto f4 = MakeUnique<FpWriteFunc>(OpenOutputFile("full4.json"));
     gIsDMDRunning = true;
 
     StatusMsg("running test mode...\n");
     RunTestMode(Move(f1), Move(f2), Move(f3), Move(f4));
-    StatusMsg("finished test mode\n");
-    exit(0);
+    StatusMsg("finished test mode; DMD is now disabled again\n");
+
+    // Continue running so that the xpcshell test can complete, but DMD no
+    // longer needs to be running.
+    gIsDMDRunning = false;
+
+  } else {
+    gIsDMDRunning = true;
   }
-
-  gIsDMDRunning = true;
 }
 
 //---------------------------------------------------------------------------
 // DMD reporting and unreporting
 //---------------------------------------------------------------------------
 
 static void
 ReportHelper(const void* aPtr, bool aReportedOnAlloc)
@@ -1830,71 +1836,88 @@ AnalyzeReports(JSONWriter& aWriter)
 }
 
 //---------------------------------------------------------------------------
 // Testing
 //---------------------------------------------------------------------------
 
 // This function checks that heap blocks that have the same stack trace but
 // different (or no) reporters get aggregated separately.
-void foo()
+void Foo(int aSeven)
 {
-   char* a[6];
-   for (int i = 0; i < 6; i++) {
-      a[i] = (char*) malloc(128 - 16*i);
-   }
+  char* a[6];
+  for (int i = 0; i < aSeven - 1; i++) {
+    a[i] = (char*) malloc(128 - 16*i);
+  }
 
-   for (int i = 0; i <= 1; i++)
-      Report(a[i]);                     // reported
-   Report(a[2]);                        // reported
-   Report(a[3]);                        // reported
-   // a[4], a[5] unreported
+  for (int i = 0; i < aSeven - 5; i++) {
+    Report(a[i]);                   // reported
+  }
+  Report(a[2]);                     // reported
+  Report(a[3]);                     // reported
+  // a[4], a[5] unreported
 }
 
 // This stops otherwise-unused variables from being optimized away.
 static void
-UseItOrLoseIt(void* a)
+UseItOrLoseIt(void* aPtr, int aSeven)
 {
   char buf[64];
-  sprintf(buf, "%p\n", a);
-  fwrite(buf, 1, strlen(buf) + 1, stderr);
+  int n = sprintf(buf, "%p\n", aPtr);
+  if (n == 20 + aSeven) {
+    fprintf(stderr, "well, that is surprising");
+  }
 }
 
-// The output from this should be tested with check_test_output.py.  It's been
-// tested on Linux64, and probably will give different results on other
-// platforms.
+// The output from this function feeds into DMD's xpcshell test.
 static void
 RunTestMode(UniquePtr<FpWriteFunc> aF1, UniquePtr<FpWriteFunc> aF2,
             UniquePtr<FpWriteFunc> aF3, UniquePtr<FpWriteFunc> aF4)
 {
+  // This test relies on the compiler not doing various optimizations, such as
+  // eliding unused malloc() calls or unrolling loops with fixed iteration
+  // counts. So we want a constant value that the compiler can't determine
+  // statically, and we use that in various ways to prevent the above
+  // optimizations from happening.
+  //
+  // This code always sets |seven| to the value 7. It works because we know
+  // that "--mode=test" must be within the DMD environment variable if we reach
+  // here, but the compiler almost certainly does not.
+  //
+  char* env = getenv("DMD");
+  char* p1 = strstr(env, "--mode=t");
+  char* p2 = strstr(p1, "test");
+  int seven = p2 - p1;
+
   // The first part of this test requires sampling to be disabled.
   gOptions->SetSampleBelowSize(1);
 
   //---------
 
   // AnalyzeReports 1.  Zero for everything.
   JSONWriter writer1(Move(aF1));
   AnalyzeReports(writer1);
 
   //---------
 
   // AnalyzeReports 2: 1 freed, 9 out of 10 unreported.
   // AnalyzeReports 3: still present and unreported.
   int i;
-  char* a;
-  for (i = 0; i < 10; i++) {
+  char* a = nullptr;
+  for (i = 0; i < seven + 3; i++) {
       a = (char*) malloc(100);
-      UseItOrLoseIt(a);
+      UseItOrLoseIt(a, seven);
   }
   free(a);
 
-  // Min-sized block.
+  // Note: 8 bytes is the smallest requested size that gives consistent
+  // behaviour across all platforms with jemalloc.
   // AnalyzeReports 2: reported.
   // AnalyzeReports 3: thrice-reported.
-  char* a2 = (char*) malloc(0);
+  char* a2 = (char*) malloc(8);
   Report(a2);
 
   // Operator new[].
   // AnalyzeReports 2: reported.
   // AnalyzeReports 3: reportedness carries over, due to ReportOnAlloc.
   char* b = new char[10];
   ReportOnAlloc(b);
 
@@ -1904,17 +1927,17 @@ RunTestMode(UniquePtr<FpWriteFunc> aF1, 
   char* b2 = new char;
   ReportOnAlloc(b2);
   free(b2);
 
   // AnalyzeReports 2: reported 4 times.
   // AnalyzeReports 3: freed, irrelevant.
   char* c = (char*) calloc(10, 3);
   Report(c);
-  for (int i = 0; i < 3; i++) {
+  for (int i = 0; i < seven - 4; i++) {
     Report(c);
   }
 
   // AnalyzeReports 2: ignored.
   // AnalyzeReports 3: irrelevant.
   Report((void*)(intptr_t)i);
 
   // jemalloc rounds this up to 8192.
@@ -1946,18 +1969,18 @@ RunTestMode(UniquePtr<FpWriteFunc> aF1, 
   free(f);
 
   // AnalyzeReports 2: ignored.
   // AnalyzeReports 3: irrelevant.
   Report((void*)(intptr_t)0x0);
 
   // AnalyzeReports 2: mixture of reported and unreported.
   // AnalyzeReports 3: all unreported.
-  foo();
-  foo();
+  Foo(seven);
+  Foo(seven);
 
   // AnalyzeReports 2: twice-reported.
   // AnalyzeReports 3: twice-reported.
   char* g1 = (char*) malloc(77);
   ReportOnAlloc(g1);
   ReportOnAlloc(g1);
 
   // AnalyzeReports 2: twice-reported.
@@ -1972,24 +1995,24 @@ RunTestMode(UniquePtr<FpWriteFunc> aF1, 
   ReportOnAlloc(g3);
   Report(g3);
 
   // All the odd-ball ones.
   // AnalyzeReports 2: all unreported.
   // AnalyzeReports 3: all freed, irrelevant.
   // XXX: no memalign on Mac
 //void* x = memalign(64, 65);           // rounds up to 128
-//UseItOrLoseIt(x);
+//UseItOrLoseIt(x, seven);
   // XXX: posix_memalign doesn't work on B2G
 //void* y;
 //posix_memalign(&y, 128, 129);         // rounds up to 256
-//UseItOrLoseIt(y);
+//UseItOrLoseIt(y, seven);
   // XXX: valloc doesn't work on Windows.
 //void* z = valloc(1);                  // rounds up to 4096
-//UseItOrLoseIt(z);
+//UseItOrLoseIt(z, seven);
 //aligned_alloc(64, 256);               // XXX: C11 only
 
   // AnalyzeReports 2.
   JSONWriter writer2(Move(aF2));
   AnalyzeReports(writer2);
 
   //---------
 
@@ -2014,59 +2037,59 @@ RunTestMode(UniquePtr<FpWriteFunc> aF1, 
 
   gOptions->SetSampleBelowSize(128);
 
   char* s;
 
   // This equals the sample size, and so is reported exactly.  It should be
   // listed before records of the same size that are sampled.
   s = (char*) malloc(128);
-  UseItOrLoseIt(s);
+  UseItOrLoseIt(s, seven);
 
   // This exceeds the sample size, and so is reported exactly.
   s = (char*) malloc(144);
-  UseItOrLoseIt(s);
+  UseItOrLoseIt(s, seven);
 
   // These together constitute exactly one sample.
-  for (int i = 0; i < 16; i++) {
+  for (int i = 0; i < seven + 9; i++) {
     s = (char*) malloc(8);
-    UseItOrLoseIt(s);
+    UseItOrLoseIt(s, seven);
   }
   MOZ_ASSERT(gSmallBlockActualSizeCounter == 0);
 
   // These fall 8 bytes short of a full sample.
-  for (int i = 0; i < 15; i++) {
+  for (int i = 0; i < seven + 8; i++) {
     s = (char*) malloc(8);
-    UseItOrLoseIt(s);
+    UseItOrLoseIt(s, seven);
   }
   MOZ_ASSERT(gSmallBlockActualSizeCounter == 120);
 
   // This exceeds the sample size, and so is recorded exactly.
   s = (char*) malloc(256);
-  UseItOrLoseIt(s);
+  UseItOrLoseIt(s, seven);
   MOZ_ASSERT(gSmallBlockActualSizeCounter == 120);
 
   // This gets more than to a full sample from the |i < 15| loop above.
   s = (char*) malloc(96);
-  UseItOrLoseIt(s);
+  UseItOrLoseIt(s, seven);
   MOZ_ASSERT(gSmallBlockActualSizeCounter == 88);
 
   // This gets to another full sample.
-  for (int i = 0; i < 5; i++) {
+  for (int i = 0; i < seven - 2; i++) {
     s = (char*) malloc(8);
-    UseItOrLoseIt(s);
+    UseItOrLoseIt(s, seven);
   }
   MOZ_ASSERT(gSmallBlockActualSizeCounter == 0);
 
   // This allocates 16, 32, ..., 128 bytes, which results in a heap block
   // record that contains a mix of sample and non-sampled blocks, and so should
   // be printed with '~' signs.
-  for (int i = 1; i <= 8; i++) {
+  for (int i = 1; i <= seven + 1; i++) {
     s = (char*) malloc(i * 16);
-    UseItOrLoseIt(s);
+    UseItOrLoseIt(s, seven);
   }
   MOZ_ASSERT(gSmallBlockActualSizeCounter == 64);
 
   // At the end we're 64 bytes into the current sample so we report ~1,424
   // bytes of allocation overall, which is 64 less than the real value 1,488.
 
   // AnalyzeReports 4.
   JSONWriter writer4(Move(aF4));
deleted file mode 100755
--- a/memory/replace/dmd/check_test_output.py
+++ /dev/null
@@ -1,127 +0,0 @@
-#! /usr/bin/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/.
-
-"""This script takes the file produced by DMD's test mode and checks its
-correctness.
-
-It produces the following output files: $TMP/full-{fixed,filtered,diff}.dmd.
-
-It runs the appropriate fix* script to get nice stack traces.  It also
-filters out platform-specific details from the test output file.
-
-Note: you must run this from the same directory that you invoked DMD's test
-mode, otherwise the fix* script will not work properly, because some of the
-paths in the test output are relative.
-
-"""
-
-from __future__ import print_function
-
-import os
-import platform
-import re
-import subprocess
-import sys
-import tempfile
-
-def test(src_dir, kind, options, i):
-    # Filenames
-    tmp_dir = tempfile.gettempdir()
-    in_name        = os.path.join(src_dir, "full{:d}.json".format(i))
-    fixed_name     = os.path.join(tmp_dir, "full-{:}-fixed{:d}.json".format(kind, i))
-    converted_name = os.path.join(tmp_dir, "full-{:}-converted{:d}.txt".format(kind, i))
-    filtered_name  = os.path.join(tmp_dir, "full-{:}-filtered{:d}.txt".format(kind, i))
-    diff_name      = os.path.join(tmp_dir, "full-{:}-diff{:d}.txt".format(kind, i))
-    expected_name  = os.path.join(src_dir, "memory", "replace", "dmd", "test", "full-{:}-expected{:d}.txt".format(kind, i))
-
-    # Fix stack traces
-
-    sys_name = platform.system()
-    fix = os.path.join(src_dir, "tools", "rb")
-    if sys_name == "Linux":
-        fix = os.path.join(fix, "fix_linux_stack.py")
-    elif sys_name == "Darwin":
-        fix = os.path.join(fix, "fix_macosx_stack.py")
-    else:
-        print("unhandled platform: " + sys_name, file=sys.stderr)
-        sys.exit(1)
-
-    subprocess.call(fix, stdin=open(in_name, "r"),
-                         stdout=open(fixed_name, "w"))
-
-    # Convert from JSON
-
-    convert = [os.path.join(src_dir, "memory", "replace", "dmd", "dmd.py")] + \
-               options + ['--no-fix-stacks', fixed_name]
-    subprocess.call(convert, stdout=open(converted_name, "w"))
-
-    # Filter output
-
-    # In heap block records we filter out most stack frames.  The only thing
-    # we leave behind is a "DMD.cpp" entry if we see one or more frames that
-    # have DMD.cpp in them.  There is simply too much variation to do anything
-    # better than that.
-
-    with open(converted_name, "r") as fin, \
-         open(filtered_name, "w") as fout:
-
-        test_frame_re = re.compile(r".*(DMD.cpp)")
-
-        for line in fin:
-            if re.match(r"  (Allocated at {|Reported( again)? at {)", line):
-                # It's a heap block record.
-                print(line, end='', file=fout)
-
-                # Filter the stack trace -- print a single line if we see one
-                # or more frames involving DMD.cpp.
-                seen_DMD_frame = False
-                for frame in fin:
-                    if re.match(r"    ", frame):
-                        m = test_frame_re.match(frame)
-                        if m:
-                            seen_DMD_frame = True
-                    else:
-                        # We're past the stack trace.
-                        if seen_DMD_frame:
-                            print("    ... DMD.cpp", file=fout)
-                        print(frame, end='', file=fout)
-                        break
-
-            else:
-                # A line that needs no special handling.  Copy it through.
-                print(line, end='', file=fout)
-
-    # Compare with expected output
-
-    ret = subprocess.call(["diff", "-u", expected_name, filtered_name],
-                          stdout=open(diff_name, "w"))
-
-    if ret == 0:
-        print("TEST-PASS | {:} {:d} | ok".format(kind, i))
-    else:
-        print("TEST-UNEXPECTED-FAIL | {:} {:d} | mismatch".format(kind, i))
-        print("Output files:")
-        print("- " + fixed_name);
-        print("- " + converted_name);
-        print("- " + filtered_name);
-        print("- " + diff_name);
-
-
-def main():
-    if (len(sys.argv) != 2):
-        print("usage:", sys.argv[0], "<topsrcdir>")
-        sys.exit(1)
-
-    src_dir = sys.argv[1]
-
-    ntests = 4
-    for i in range(1, ntests+1):
-        test(src_dir, "reports", [], i)
-        test(src_dir, "heap", ["--ignore-reports"], i)
-
-
-if __name__ == "__main__":
-    main()
--- a/memory/replace/dmd/dmd.py
+++ b/memory/replace/dmd/dmd.py
@@ -97,17 +97,18 @@ def parseCommandLine():
         return value
 
     description = '''
 Analyze heap data produced by DMD.
 If no files are specified, read from stdin.
 Write to stdout unless -o/--output is specified.
 Stack traces are fixed to show function names, filenames and line numbers
 unless --no-fix-stacks is specified; stack fixing modifies the original file
-and may take some time.
+and may take some time. If specified, the BREAKPAD_SYMBOLS_PATH environment
+variable is used to find breakpad symbols for stack fixing.
 '''
     p = argparse.ArgumentParser(description=description)
 
     p.add_argument('-o', '--output', type=argparse.FileType('w'),
                    help='output file; stdout if unspecified')
 
     p.add_argument('-f', '--max-frames', type=range_1_24,
                    help='maximum number of frames to consider in each trace')
@@ -124,16 +125,19 @@ and may take some time.
                    help='ignore allocation functions at the start of traces')
 
     p.add_argument('-b', '--show-all-block-sizes', action='store_true',
                    help='show individual block sizes for each record')
 
     p.add_argument('--no-fix-stacks', action='store_true',
                    help='do not fix stacks')
 
+    p.add_argument('--filter-stacks-for-testing', action='store_true',
+                   help='filter stack traces; only useful for testing purposes')
+
     p.add_argument('input_file', type=argparse.FileType('r'))
 
     return p.parse_args(sys.argv[1:])
 
 
 # Fix stacks if necessary: first write the output to a tempfile, then replace
 # the original file with it.
 def fixStackTraces(args):
@@ -299,20 +303,36 @@ def main():
     def plural(n):
         return '' if n == 1 else 's'
 
     # Prints to stdout, or to file if -o/--output was specified.
     def out(*arguments, **kwargs):
         print(*arguments, file=args.output, **kwargs)
 
     def printStack(traceTable, frameTable, traceKey):
+        frameKeys = traceTable[traceKey]
+        fmt = '    #{:02d}{:}'
+
+        if args.filter_stacks_for_testing:
+            # If any frame has "DMD.cpp" or "replace_malloc.c" in its
+            # description -- as should be the case for every stack trace when
+            # running DMD in test mode -- we replace the entire trace with a
+            # single, predictable frame. There is too much variation in the
+            # stack traces across different machines and platforms to do more
+            # specific matching.
+            for frameKey in frameKeys:
+                frameDesc = frameTable[frameKey]
+                if 'DMD.cpp' in frameDesc or 'replace_malloc.c' in frameDesc:
+                    out(fmt.format(1, ': ... DMD.cpp ...'))
+                    return
+
         # The frame number is always '#00' (see DMD.h for why), so we have to
         # replace that with the correct frame number.
         for n, frameKey in enumerate(traceTable[traceKey], start=1):
-            out('    #{:02d}{:}'.format(n, frameTable[frameKey][3:]))
+            out(fmt.format(n, frameTable[frameKey][3:]))
 
     def printRecords(recordKind, records, heapUsableSize):
         RecordKind = recordKind.capitalize()
         out(separator)
         numRecords = len(records)
         cmpRecords = sortByChoices[args.sort_by]
         sortedRecords = sorted(records.values(), cmp=cmpRecords, reverse=True)
         kindBlocks = 0
--- a/memory/replace/dmd/moz.build
+++ b/memory/replace/dmd/moz.build
@@ -27,8 +27,13 @@ if CONFIG['MOZ_OPTIMIZE']:
     DEFINES['MOZ_OPTIMIZE'] = True
 
 DISABLE_STL_WRAPPING = True
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     OS_LIBS += [
         'dbghelp',
     ]
+
+XPCSHELL_TESTS_MANIFESTS += [
+    'test/xpcshell.ini',
+]
+
--- a/memory/replace/dmd/test/full-heap-expected2.txt
+++ b/memory/replace/dmd/test/full-heap-expected2.txt
@@ -7,116 +7,116 @@ Invocation {
 
 #-----------------------------------------------------------------
 
 Live {
   1 block in heap block record 1 of 12
   8,192 bytes (4,097 requested / 4,095 slop)
   67.77% of the heap (67.77% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 2 of 12
   1,024 bytes (1,023 requested / 1 slop)
   8.47% of the heap (76.24% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   9 blocks in heap block record 3 of 12
   1,008 bytes (900 requested / 108 slop)
   8.34% of the heap (84.58% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   6 blocks in heap block record 4 of 12
   528 bytes (528 requested / 0 slop)
   4.37% of the heap (88.95% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   6 blocks in heap block record 5 of 12
   528 bytes (528 requested / 0 slop)
   4.37% of the heap (93.32% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 6 of 12
   512 bytes (512 requested / 0 slop)
   4.24% of the heap (97.55% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 7 of 12
   80 bytes (79 requested / 1 slop)
   0.66% of the heap (98.21% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 8 of 12
   80 bytes (78 requested / 2 slop)
   0.66% of the heap (98.87% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 9 of 12
   80 bytes (77 requested / 3 slop)
   0.66% of the heap (99.54% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 10 of 12
   32 bytes (30 requested / 2 slop)
   0.26% of the heap (99.80% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 11 of 12
   16 bytes (10 requested / 6 slop)
   0.13% of the heap (99.93% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 12 of 12
-  8 bytes (0 requested / 8 slop)
+  8 bytes (8 requested / 0 slop)
   0.07% of the heap (100.00% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 #-----------------------------------------------------------------
 
 Summary {
   Total: 12,088 bytes in 30 blocks
 }
--- a/memory/replace/dmd/test/full-heap-expected3.txt
+++ b/memory/replace/dmd/test/full-heap-expected3.txt
@@ -7,89 +7,89 @@ Invocation {
 
 #-----------------------------------------------------------------
 
 Live {
   9 blocks in heap block record 1 of 9
   1,008 bytes (900 requested / 108 slop)
   35.49% of the heap (35.49% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   6 blocks in heap block record 2 of 9
   528 bytes (528 requested / 0 slop)
   18.59% of the heap (54.08% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   6 blocks in heap block record 3 of 9
   528 bytes (528 requested / 0 slop)
   18.59% of the heap (72.68% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 4 of 9
   512 bytes (512 requested / 0 slop)
   18.03% of the heap (90.70% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 5 of 9
   80 bytes (79 requested / 1 slop)
   2.82% of the heap (93.52% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 6 of 9
   80 bytes (78 requested / 2 slop)
   2.82% of the heap (96.34% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 7 of 9
   80 bytes (77 requested / 3 slop)
   2.82% of the heap (99.15% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 8 of 9
   16 bytes (10 requested / 6 slop)
   0.56% of the heap (99.72% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 9 of 9
-  8 bytes (0 requested / 8 slop)
+  8 bytes (8 requested / 0 slop)
   0.28% of the heap (100.00% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 #-----------------------------------------------------------------
 
 Summary {
   Total: 2,840 bytes in 27 blocks
 }
--- a/memory/replace/dmd/test/full-heap-expected4.txt
+++ b/memory/replace/dmd/test/full-heap-expected4.txt
@@ -7,71 +7,71 @@ Invocation {
 
 #-----------------------------------------------------------------
 
 Live {
   ~4 blocks in heap block record 1 of 7
   ~512 bytes (~512 requested / ~0 slop)
   35.96% of the heap (35.96% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 2 of 7
   256 bytes (256 requested / 0 slop)
   17.98% of the heap (53.93% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 3 of 7
   144 bytes (144 requested / 0 slop)
   10.11% of the heap (64.04% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 4 of 7
   128 bytes (128 requested / 0 slop)
   8.99% of the heap (73.03% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   ~1 block in heap block record 5 of 7
   ~128 bytes (~128 requested / ~0 slop)
   8.99% of the heap (82.02% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   ~1 block in heap block record 6 of 7
   ~128 bytes (~128 requested / ~0 slop)
   8.99% of the heap (91.01% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Live {
   ~1 block in heap block record 7 of 7
   ~128 bytes (~128 requested / ~0 slop)
   8.99% of the heap (100.00% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 #-----------------------------------------------------------------
 
 Summary {
   Total: ~1,424 bytes in ~10 blocks
 }
--- a/memory/replace/dmd/test/full-reports-expected2.txt
+++ b/memory/replace/dmd/test/full-reports-expected2.txt
@@ -8,248 +8,248 @@ Invocation {
 #-----------------------------------------------------------------
 
 Twice-reported {
   1 block in heap block record 1 of 4
   80 bytes (79 requested / 1 slop)
   0.66% of the heap (0.66% cumulative)
   29.41% of twice-reported (29.41% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported again at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Twice-reported {
   1 block in heap block record 2 of 4
   80 bytes (78 requested / 2 slop)
   0.66% of the heap (1.32% cumulative)
   29.41% of twice-reported (58.82% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported again at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Twice-reported {
   1 block in heap block record 3 of 4
   80 bytes (77 requested / 3 slop)
   0.66% of the heap (1.99% cumulative)
   29.41% of twice-reported (88.24% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported again at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Twice-reported {
   1 block in heap block record 4 of 4
   32 bytes (30 requested / 2 slop)
   0.26% of the heap (2.25% cumulative)
   11.76% of twice-reported (100.00% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported again at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 #-----------------------------------------------------------------
 
 Unreported {
   9 blocks in heap block record 1 of 3
   1,008 bytes (900 requested / 108 slop)
   8.34% of the heap (8.34% cumulative)
   81.82% of unreported (81.82% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Unreported {
   2 blocks in heap block record 2 of 3
   112 bytes (112 requested / 0 slop)
   0.93% of the heap (9.27% cumulative)
   9.09% of unreported (90.91% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Unreported {
   2 blocks in heap block record 3 of 3
   112 bytes (112 requested / 0 slop)
   0.93% of the heap (10.19% cumulative)
   9.09% of unreported (100.00% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 #-----------------------------------------------------------------
 
 Once-reported {
   1 block in heap block record 1 of 11
   8,192 bytes (4,097 requested / 4,095 slop)
   67.77% of the heap (67.77% cumulative)
   77.40% of once-reported (77.40% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Once-reported {
   1 block in heap block record 2 of 11
   1,024 bytes (1,023 requested / 1 slop)
   8.47% of the heap (76.24% cumulative)
   9.67% of once-reported (87.07% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Once-reported {
   1 block in heap block record 3 of 11
   512 bytes (512 requested / 0 slop)
   4.24% of the heap (80.48% cumulative)
   4.84% of once-reported (91.91% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Once-reported {
   2 blocks in heap block record 4 of 11
   240 bytes (240 requested / 0 slop)
   1.99% of the heap (82.46% cumulative)
   2.27% of once-reported (94.18% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Once-reported {
   2 blocks in heap block record 5 of 11
   240 bytes (240 requested / 0 slop)
   1.99% of the heap (84.45% cumulative)
   2.27% of once-reported (96.45% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Once-reported {
   1 block in heap block record 6 of 11
   96 bytes (96 requested / 0 slop)
   0.79% of the heap (85.24% cumulative)
   0.91% of once-reported (97.35% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Once-reported {
   1 block in heap block record 7 of 11
   96 bytes (96 requested / 0 slop)
   0.79% of the heap (86.04% cumulative)
   0.91% of once-reported (98.26% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Once-reported {
   1 block in heap block record 8 of 11
   80 bytes (80 requested / 0 slop)
   0.66% of the heap (86.70% cumulative)
   0.76% of once-reported (99.02% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Once-reported {
   1 block in heap block record 9 of 11
   80 bytes (80 requested / 0 slop)
   0.66% of the heap (87.36% cumulative)
   0.76% of once-reported (99.77% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Once-reported {
   1 block in heap block record 10 of 11
   16 bytes (10 requested / 6 slop)
   0.13% of the heap (87.49% cumulative)
   0.15% of once-reported (99.92% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Once-reported {
   1 block in heap block record 11 of 11
-  8 bytes (0 requested / 8 slop)
+  8 bytes (8 requested / 0 slop)
   0.07% of the heap (87.56% cumulative)
   0.08% of once-reported (100.00% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 #-----------------------------------------------------------------
 
 Summary {
   Total:                12,088 bytes (100.00%) in      30 blocks (100.00%)
   Unreported:            1,232 bytes ( 10.19%) in      13 blocks ( 43.33%)
--- a/memory/replace/dmd/test/full-reports-expected3.txt
+++ b/memory/replace/dmd/test/full-reports-expected3.txt
@@ -8,125 +8,125 @@ Invocation {
 #-----------------------------------------------------------------
 
 Twice-reported {
   1 block in heap block record 1 of 2
   80 bytes (77 requested / 3 slop)
   2.82% of the heap (2.82% cumulative)
   90.91% of twice-reported (90.91% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported again at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Twice-reported {
   1 block in heap block record 2 of 2
-  8 bytes (0 requested / 8 slop)
+  8 bytes (8 requested / 0 slop)
   0.28% of the heap (3.10% cumulative)
   9.09% of twice-reported (100.00% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported again at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 #-----------------------------------------------------------------
 
 Unreported {
   9 blocks in heap block record 1 of 3
   1,008 bytes (900 requested / 108 slop)
   35.49% of the heap (35.49% cumulative)
   48.84% of unreported (48.84% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Unreported {
   6 blocks in heap block record 2 of 3
   528 bytes (528 requested / 0 slop)
   18.59% of the heap (54.08% cumulative)
   25.58% of unreported (74.42% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Unreported {
   6 blocks in heap block record 3 of 3
   528 bytes (528 requested / 0 slop)
   18.59% of the heap (72.68% cumulative)
   25.58% of unreported (100.00% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 #-----------------------------------------------------------------
 
 Once-reported {
   1 block in heap block record 1 of 4
   512 bytes (512 requested / 0 slop)
   18.03% of the heap (18.03% cumulative)
   74.42% of once-reported (74.42% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Once-reported {
   1 block in heap block record 2 of 4
   80 bytes (79 requested / 1 slop)
   2.82% of the heap (20.85% cumulative)
   11.63% of once-reported (86.05% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Once-reported {
   1 block in heap block record 3 of 4
   80 bytes (78 requested / 2 slop)
   2.82% of the heap (23.66% cumulative)
   11.63% of once-reported (97.67% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Once-reported {
   1 block in heap block record 4 of 4
   16 bytes (10 requested / 6 slop)
   0.56% of the heap (24.23% cumulative)
   2.33% of once-reported (100.00% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
   Reported at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 #-----------------------------------------------------------------
 
 Summary {
   Total:                 2,840 bytes (100.00%) in      27 blocks (100.00%)
   Unreported:            2,064 bytes ( 72.68%) in      21 blocks ( 77.78%)
--- a/memory/replace/dmd/test/full-reports-expected4.txt
+++ b/memory/replace/dmd/test/full-reports-expected4.txt
@@ -12,77 +12,77 @@ Invocation {
 #-----------------------------------------------------------------
 
 Unreported {
   ~4 blocks in heap block record 1 of 7
   ~512 bytes (~512 requested / ~0 slop)
   35.96% of the heap (35.96% cumulative)
   35.96% of unreported (35.96% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Unreported {
   1 block in heap block record 2 of 7
   256 bytes (256 requested / 0 slop)
   17.98% of the heap (53.93% cumulative)
   17.98% of unreported (53.93% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Unreported {
   1 block in heap block record 3 of 7
   144 bytes (144 requested / 0 slop)
   10.11% of the heap (64.04% cumulative)
   10.11% of unreported (64.04% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Unreported {
   1 block in heap block record 4 of 7
   128 bytes (128 requested / 0 slop)
   8.99% of the heap (73.03% cumulative)
   8.99% of unreported (73.03% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Unreported {
   ~1 block in heap block record 5 of 7
   ~128 bytes (~128 requested / ~0 slop)
   8.99% of the heap (82.02% cumulative)
   8.99% of unreported (82.02% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Unreported {
   ~1 block in heap block record 6 of 7
   ~128 bytes (~128 requested / ~0 slop)
   8.99% of the heap (91.01% cumulative)
   8.99% of unreported (91.01% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 Unreported {
   ~1 block in heap block record 7 of 7
   ~128 bytes (~128 requested / ~0 slop)
   8.99% of the heap (100.00% cumulative)
   8.99% of unreported (100.00% cumulative)
   Allocated at {
-    ... DMD.cpp
+    #01: ... DMD.cpp ...
   }
 }
 
 #-----------------------------------------------------------------
 
 # no once-reported heap blocks
 
 #-----------------------------------------------------------------
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/test_dmd.js
@@ -0,0 +1,87 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-*/
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components
+
+Cu.import("resource://gre/modules/FileUtils.jsm");
+
+// The xpcshell test harness sets PYTHON so we can read it here.
+let gEnv = Cc["@mozilla.org/process/environment;1"]
+             .getService(Ci.nsIEnvironment);
+let gPythonName = gEnv.get("PYTHON");
+
+// If we're testing locally, the script is in "CurProcD". Otherwise, it is in
+// another location that we have to find.
+let gDmdScriptFile = FileUtils.getFile("CurProcD", ["dmd.py"]);
+if (!gDmdScriptFile.exists()) {
+  gDmdScriptFile = FileUtils.getFile("CurWorkD", []);
+  while (gDmdScriptFile.path.contains("xpcshell")) {
+    gDmdScriptFile = gDmdScriptFile.parent;
+  }
+  gDmdScriptFile.append("bin");
+  gDmdScriptFile.append("dmd.py");
+}
+
+function test(aJsonFile, aKind, aOptions, aN) {
+  // DMD writes the JSON files to CurWorkD, so we do likewise here with
+  // |actualFile| for consistency. It is removed once we've finished.
+  let expectedFile =
+    FileUtils.getFile("CurWorkD",
+                      ["full-" + aKind + "-expected" + aN + ".txt"]);
+  let actualFile =
+    FileUtils.getFile("CurWorkD",
+                      ["full-" + aKind + "-actual"   + aN + ".txt"]);
+
+  // Run dmd.py on the JSON file, producing |actualFile|.
+
+  let pythonFile = new FileUtils.File(gPythonName);
+  let pythonProcess = Cc["@mozilla.org/process/util;1"]
+                        .createInstance(Components.interfaces.nsIProcess);
+  pythonProcess.init(pythonFile);
+
+  let args = [
+    gDmdScriptFile.path,
+    "--filter-stacks-for-testing",
+    "-o", actualFile.path
+  ];
+  args = args.concat(aOptions);
+  args.push(aJsonFile.path);
+
+  pythonProcess.run(/* blocking = */true, args, args.length);
+
+  // Compare |expectedFile| with |actualFile|. Difference are printed to
+  // stdout.
+
+  let diffFile = new FileUtils.File("/usr/bin/diff");
+  let diffProcess = Cc["@mozilla.org/process/util;1"]
+                      .createInstance(Components.interfaces.nsIProcess);
+  // XXX: this doesn't work on Windows (bug 1076446).
+  diffProcess.init(diffFile);
+
+  args = ["-u", expectedFile.path, actualFile.path];
+  diffProcess.run(/* blocking = */true, args, args.length);
+  let success = diffProcess.exitValue == 0;
+  ok(success, aKind + " " + aN);
+
+  actualFile.remove(true);
+}
+
+function run_test() {
+  // These tests do full end-to-end testing of DMD, i.e. both the C++ code that
+  // generates the JSON output, and the script that post-processes that output.
+  // The test relies on DMD's test mode executing beforehand, in order to
+  // produce the relevant JSON files.
+  //
+  // Run these synchronously, because test() updates the full*.json files
+  // in-place (to fix stacks) when it runs dmd.py, and that's not safe to do
+  // asynchronously.
+  for (let i = 1; i <= 4; i++) {
+      let jsonFile = FileUtils.getFile("CurWorkD", ["full" + i + ".json"]);
+      test(jsonFile, "heap", ["--ignore-reports"], i);
+      test(jsonFile, "reports", [], i);
+      jsonFile.remove(true);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/xpcshell.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+support-files =
+  full-heap-expected1.txt
+  full-heap-expected2.txt
+  full-heap-expected3.txt
+  full-heap-expected4.txt
+  full-reports-expected1.txt
+  full-reports-expected2.txt
+  full-reports-expected3.txt
+  full-reports-expected4.txt
+
+[test_dmd.js]
+dmd = true
+# XXX: bug 1076446 is open for running this test on Windows, and bug 1077230 is
+# open for running it on Mac.
+run-if = os == 'linux'
--- a/testing/mochitest/Makefile.in
+++ b/testing/mochitest/Makefile.in
@@ -57,16 +57,20 @@ endif
 
 ifeq (windows,$(MOZ_WIDGET_TOOLKIT))
 TEST_HARNESS_BINS += screenshot$(BIN_SUFFIX)
 ifdef MOZ_METRO
 TEST_HARNESS_BINS += metrotestharness$(BIN_SUFFIX)
 endif
 endif
 
+ifdef MOZ_DMD
+TEST_HARNESS_BINS += dmd.py
+endif
+
 # Components / typelibs that don't get packaged with
 # the build, but that we need for the test harness.
 TEST_HARNESS_COMPONENTS := \
   test_necko.xpt \
   $(NULL)
 
 # We need the test plugin as some tests rely on it
 ifeq (Darwin,$(OS_TARGET))
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -601,16 +601,35 @@ class XPCShellTestThread(Thread):
         cmdT = self.buildCmdTestFile(name)
 
         args = self.xpcsRunArgs[:]
         if 'debug' in self.test_object:
             args.insert(0, '-d')
 
         completeCmd = cmdH + cmdT + args
 
+        if self.test_object.get('dmd') == 'true':
+            if sys.platform.startswith('linux'):
+                preloadEnvVar = 'LD_PRELOAD'
+                libdmd = os.path.join(self.xrePath, 'libdmd.so')
+            elif sys.platform == 'osx' or sys.platform == 'darwin':
+                preloadEnvVar = 'DYLD_INSERT_LIBRARIES'
+                # self.xrePath is <prefix>/Contents/Resources.
+                # We need <prefix>/Contents/MacOS/libdmd.dylib.
+                contents_dir = os.path.dirname(self.xrePath)
+                libdmd = os.path.join(contents_dir, 'MacOS', 'libdmd.dylib')
+            elif sys.platform == 'win32':
+                preloadEnvVar = 'MOZ_REPLACE_MALLOC_LIB'
+                libdmd = os.path.join(self.xrePath, 'dmd.dll')
+
+            self.env['DMD'] = '--mode=test'
+            self.env['PYTHON'] = sys.executable
+            self.env['BREAKPAD_SYMBOLS_PATH'] = self.symbolsPath
+            self.env[preloadEnvVar] = libdmd
+
         testTimeoutInterval = HARNESS_TIMEOUT
         # Allow a test to request a multiple of the timeout if it is expected to take long
         if 'requesttimeoutfactor' in self.test_object:
             testTimeoutInterval *= int(self.test_object['requesttimeoutfactor'])
 
         testTimer = None
         if not self.interactive and not self.debuggerInfo:
             testTimer = Timer(testTimeoutInterval, lambda: self.testTimeout(name, proc))