Bug 868158 - mochitests should support manifest format. r=ted
authorJoel Maher <jmaher@mozilla.com>
Fri, 02 Aug 2013 08:48:06 -0400
changeset 153412 c116372d7ad481f81b44dcd3c364d0e220dac6b0
parent 153411 284946982e36a7341b7294be06645dc91df8bbc7
child 153413 f06052c0986b0a9b392ede7d9a067e65653d7d71
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersted
bugs868158
milestone25.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 868158 - mochitests should support manifest format. r=ted
testing/mochitest/Makefile.in
testing/mochitest/browser-harness.xul
testing/mochitest/chrome-harness.js
testing/mochitest/harness.xul
testing/mochitest/jar.mn
testing/mochitest/manifestLibrary.js
testing/mochitest/mochitest_options.py
testing/mochitest/runtests.py
testing/mochitest/server.js
testing/mochitest/tests/SimpleTest/setup.js
testing/testsuite-targets.mk
--- a/testing/mochitest/Makefile.in
+++ b/testing/mochitest/Makefile.in
@@ -45,16 +45,17 @@ include $(topsrcdir)/build/automation-bu
 		$(topsrcdir)/testing/mozbase/mozdevice/mozdevice/droid.py \
 		$(topsrcdir)/testing/mozbase/mozdevice/mozdevice/Zeroconf.py \
 		$(topsrcdir)/build/automationutils.py \
 		$(topsrcdir)/build/mobile/remoteautomation.py \
 		$(topsrcdir)/build/mobile/b2gautomation.py \
 		gen_template.pl \
 		server.js \
 		chunkifyTests.js \
+		manifestLibrary.js \
 		harness.xul \
 		browser-test-overlay.xul \
 		browser-test.js \
 		cc-analyzer.js \
 		chrome-harness.js \
 		browser-harness.xul \
 		redirect.html \
 		$(topsrcdir)/build/pgo/server-locations.txt \
@@ -198,14 +199,15 @@ endif
 stage-package: stage-chromejar
 endif
 
 $(_DEST_DIR):
 	$(NSINSTALL) -D $@
 
 stage-package:
 	$(NSINSTALL) -D $(PKG_STAGE)/mochitest && $(NSINSTALL) -D $(PKG_STAGE)/bin/plugins && $(NSINSTALL) -D $(DIST)/plugins
+	cp $(DEPTH)/mozinfo.json $(PKG_STAGE)/mochitest
 	(cd $(DEPTH)/_tests/testing/mochitest/ && tar $(TAR_CREATE_FLAGS) - *) | (cd $(PKG_STAGE)/mochitest && tar -xf -)
 	@cp $(DEPTH)/mozinfo.json $(PKG_STAGE)/mochitest
 	@(cd $(DIST_BIN) && tar $(TAR_CREATE_FLAGS) - $(TEST_HARNESS_BINS)) | (cd $(PKG_STAGE)/bin && tar -xf -)
 	@(cd $(DIST_BIN)/components && tar $(TAR_CREATE_FLAGS) - $(TEST_HARNESS_COMPONENTS)) | (cd $(PKG_STAGE)/bin/components && tar -xf -)
 	(cd $(topsrcdir)/build/pgo/certs && tar $(TAR_CREATE_FLAGS) - *) | (cd $(PKG_STAGE)/certs && tar -xf -)
 	@(cd $(DIST)/plugins && tar $(TAR_CREATE_FLAGS) - $(TEST_HARNESS_PLUGINS)) | (cd $(PKG_STAGE)/bin/plugins && tar -xf -)
--- a/testing/mochitest/browser-harness.xul
+++ b/testing/mochitest/browser-harness.xul
@@ -7,16 +7,17 @@
 <window id="browserTestHarness"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         onload="TestStart();"
         title="Browser chrome tests"
         width="1024">
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/MozillaLogger.js"/>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/LogController.js"/>
   <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"/>
+  <script type="application/javascript" src="chrome://mochikit/content/manifestLibrary.js" />
   <script type="application/javascript" src="chrome://mochikit/content/chunkifyTests.js"/>
   <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
     #results {
       margin: 5px;
       background-color: window;
       -moz-user-select: text;
     }
 
@@ -162,20 +163,26 @@
           html += "<p class=\"info\">TEST-END | " + path + " | finished in " +
                   this.duration + " ms</p>";
         }
         return html;
       }
     };
 
     // Returns an array of browserTest objects for all the selected tests
-    function listTests() {
-      var links = getTestList();
-      if (!links)
-        return [];
+    function runTests() {
+      gConfig.baseurl = "chrome://mochitests/content";
+      getTestList(gConfig, loadTestList);
+    }
+
+    function loadTestList(links) {
+      if (!links) {
+        createTester({});
+        return;
+      }
 
       // load server.js in so we can share template functions
       var scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                            getService(Ci.mozIJSSubScriptLoader);
       var srvScope = {};
       scriptLoader.loadSubScript('chrome://mochikit/content/server.js',
                                  srvScope);
 
@@ -183,38 +190,38 @@
       var fileNameRegexp = /browser_.+\.js$/;
       srvScope.arrayOfTestFiles(links, fileNames, fileNameRegexp);
 
       if (gConfig.totalChunks && gConfig.thisChunk) {
         fileNames = chunkifyTests(fileNames, gConfig.totalChunks,
                                   gConfig.thisChunk, gConfig.chunkByDir);
       }
 
-      return fileNames.map(function (f) new browserTest(f));
+      createTester(fileNames.map(function (f) { return new browserTest(f); }));
     }
 
     function setStatus(aStatusString) {
       document.getElementById("status").value = aStatusString;
     }
 
-    function runTests() {
+    function createTester(links) {
       var windowMediator = Cc['@mozilla.org/appshell/window-mediator;1'].
                              getService(Ci.nsIWindowMediator);
       var winType = gConfig.testRoot == "browser" ? "navigator:browser" :
                     gConfig.testRoot == "metro" ? "navigator:browser" :
                     gConfig.testRoot == "webapprtChrome" ? "webapprt:webapp" :
                     null;
       if (!winType) {
         throw new Error("Unrecognized gConfig.testRoot: " + gConfig.testRoot);
       }
       var testWin = windowMediator.getMostRecentWindow(winType);
 
       setStatus("Running...");
       testWin.focus();
-      var Tester = new testWin.Tester(listTests(), gDumper, testsFinished);
+      var Tester = new testWin.Tester(links, gDumper, testsFinished);
       Tester.start();
     }
 
     function sum(a, b) {
       return a + b;
     }
 
     function getHTMLLogFromTests(aTests) {
--- a/testing/mochitest/chrome-harness.js
+++ b/testing/mochitest/chrome-harness.js
@@ -160,38 +160,42 @@ function zList(base, zReader, baseJarNam
  */
 function getFileListing(basePath, testPath, dir, srvScope)
 {
   var uri = getResolvedURI(basePath);
   var chromeDir = getChromeDir(uri);
   chromeDir.appendRelativePath(dir);
   basePath += '/' + dir;
 
+  if (testPath == "false" || testPath == false) {
+    testPath = "";
+  }
+
   var ioSvc = Components.classes["@mozilla.org/network/io-service;1"].
               getService(Components.interfaces.nsIIOService);
   var testsDirURI = ioSvc.newFileURI(chromeDir);
   var testsDir = ioSvc.newURI(testPath, null, testsDirURI)
                   .QueryInterface(Components.interfaces.nsIFileURL).file;
 
   if (testPath != undefined) {
     var extraPath = testPath;
     
     var fileNameRegexp = /(browser|test)_.+\.(xul|html|js)$/;
 
     // Invalid testPath...
     if (!testsDir.exists())
       return null;
 
     if (testsDir.isFile()) {
-      if (fileNameRegexp.test(testsDir.leafName))
+      if (fileNameRegexp.test(testsDir.leafName)) {
         var singlePath = basePath + '/' + testPath;
         var links = {};
         links[singlePath] = true;
         return links;
-
+      }
       // We were passed a file that's not a test...
       return null;
     }
 
     // otherwise, we were passed a directory of tests
     basePath += "/" + testPath;
   }
   var [links, count] = srvScope.list(basePath, testsDir, true);
@@ -317,72 +321,84 @@ function buildRelativePath(jarentryname,
 
   for (var i = baseParts.length; i < parts.length; i++) {
     targetFile.append(parts[i]);
   }
 
   return targetFile;
 }
 
-function readConfig() {
+function readConfig(filename) {
+  filename = filename || "testConfig.js";
+
   var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"].
                     getService(Components.interfaces.nsIProperties);
   var configFile = fileLocator.get("ProfD", Components.interfaces.nsIFile);
-  configFile.append("testConfig.js");
+  configFile.append(filename);
 
   if (!configFile.exists())
     return {};
 
   var fileInStream = Components.classes["@mozilla.org/network/file-input-stream;1"].
                      createInstance(Components.interfaces.nsIFileInputStream);
   fileInStream.init(configFile, -1, 0, 0);
 
   var str = NetUtil.readInputStreamToString(fileInStream, fileInStream.available());
   fileInStream.close();
   return JSON.parse(str);
 }
 
-function getTestList() {
-  var params = {};
+function registerTests() {
+  var testsURI = Components.classes["@mozilla.org/file/directory_service;1"].
+                 getService(Components.interfaces.nsIProperties).
+                 get("ProfD", Components.interfaces.nsILocalFile);
+  testsURI.append("tests.manifest");
+  var ioSvc = Components.classes["@mozilla.org/network/io-service;1"].
+              getService(Components.interfaces.nsIIOService);
+  var manifestFile = ioSvc.newFileURI(testsURI).
+                     QueryInterface(Components.interfaces.nsIFileURL).file;
+
+  Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar).
+                     autoRegister(manifestFile);
+}
+
+function getTestList(params, callback) {
+  registerTests();
+
+  var baseurl = 'chrome://mochitests/content';
   if (window.parseQueryString) {
     params = parseQueryString(location.search.substring(1), true);
   }
+  if (!params.baseurl) {
+    params.baseurl = baseurl;
+  }
 
   var config = readConfig();
   for (p in params) {
     if (params[p] == 1) {
       config[p] = true;
     } else if (params[p] == 0) {
       config[p] = false;
     } else {
       config[p] = params[p];
     }
   }
   params = config;
+  if (params.manifestFile) {
+    getTestManifest("http://mochi.test:8888/" + params.manifestFile, params, callback);
+    return;
+  }
 
-  var baseurl = 'chrome://mochitests/content';
-  var testsURI = Components.classes["@mozilla.org/file/directory_service;1"]
-                      .getService(Components.interfaces.nsIProperties)
-                      .get("ProfD", Components.interfaces.nsILocalFile);
-  testsURI.append("tests.manifest");
-  var ioSvc = Components.classes["@mozilla.org/network/io-service;1"].
-              getService(Components.interfaces.nsIIOService);
-  var manifestFile = ioSvc.newFileURI(testsURI)
-                  .QueryInterface(Components.interfaces.nsIFileURL).file;
-
-  Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar).
-    autoRegister(manifestFile);
-
+  var links = {};
   // load server.js in so we can share template functions
   var scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                        getService(Ci.mozIJSSubScriptLoader);
   var srvScope = {};
   scriptLoader.loadSubScript('chrome://mochikit/content/server.js',
                              srvScope);
-  var links;
 
   if (getResolvedURI(baseurl).JARFile) {
     links = getMochitestJarListing(baseurl, params.testPath, params.testRoot);
   } else {
     links = getFileListing(baseurl, params.testPath, params.testRoot, srvScope);
   }
-  return links;
+  callback(links);
 }
--- a/testing/mochitest/harness.xul
+++ b/testing/mochitest/harness.xul
@@ -16,30 +16,34 @@
   <script type="text/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/ChromePowers.js"/>
   <script type="text/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/TestRunner.js"/>
   <script type="text/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/MozillaLogger.js"/>
   <script type="application/javascript"
           src="chrome://mochikit/content/chrome-harness.js" />
+  <script type="application/javascript"
+          src="chrome://mochikit/content/manifestLibrary.js" />
   <script type="text/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/setup.js" />
   <script type="application/javascript;version=1.7"><![CDATA[
 
 if (Cc === undefined) {
   var Cc = Components.classes;
   var Ci = Components.interfaces;
 }
 
 function loadTests()
 {
   window.removeEventListener("load", loadTests, false);
-  links = getTestList();
+  getTestList({}, linkAndHookup);
+}
  
+function linkAndHookup(links) {
   // load server.js in so we can share template functions
   var scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                        getService(Ci.mozIJSSubScriptLoader);
   var srvScope = {};
   scriptLoader.loadSubScript('chrome://mochikit/content/server.js',
                              srvScope);
 
   // generate our test list
--- a/testing/mochitest/jar.mn
+++ b/testing/mochitest/jar.mn
@@ -4,16 +4,17 @@ mochikit.jar:
   content/browser-test.js (browser-test.js)
   content/browser-test-overlay.xul (browser-test-overlay.xul)
   content/cc-analyzer.js (cc-analyzer.js)
   content/chrome-harness.js (chrome-harness.js)
   content/harness.xul (harness.xul)
   content/redirect.html (redirect.html)
   content/server.js (server.js)
   content/chunkifyTests.js (chunkifyTests.js)
+  content/manifestLibrary.js (manifestLibrary.js)
   content/dynamic/getMyDirectory.sjs (dynamic/getMyDirectory.sjs)
   content/static/harness.css (static/harness.css)
   content/tests/SimpleTest/ChromePowers.js (tests/SimpleTest/ChromePowers.js)
   content/tests/SimpleTest/EventUtils.js (tests/SimpleTest/EventUtils.js)
   content/tests/SimpleTest/ChromeUtils.js (tests/SimpleTest/ChromeUtils.js)
   content/tests/SimpleTest/LogController.js (tests/SimpleTest/LogController.js)
   content/tests/SimpleTest/MozillaLogger.js (../specialpowers/content/MozillaLogger.js)
   content/tests/SimpleTest/SpecialPowersObserverAPI.js (../specialpowers/content/SpecialPowersObserverAPI.js)
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/manifestLibrary.js
@@ -0,0 +1,139 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 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/. */
+
+function parseTestManifest(testManifest, params, callback) {
+  var links = {};
+  var paths = [];
+
+  // Support --test-manifest format for mobile/b2g
+  if ("runtests" in testManifest || "excludetests" in testManifest) {
+    callback(testManifest);
+    return;
+  }
+
+  // For mochitest-chrome and mochitest-browser-chrome harnesses, we 
+  // define tests as links[testname] = true.
+  // For mochitest-plain, we define lists as an array of testnames.
+  for (var obj of testManifest['tests']) {
+    var path = obj['path'];
+    if (params.testRoot != 'tests' && params.testRoot !== undefined) {
+      links[params.baseurl + '/' + params.testRoot + '/' + path] = true
+    } else {
+      paths.push(params.testPrefix + path);
+    }
+  }
+  if (paths.length > 0) {
+    callback(paths);
+  } else {
+    callback(links);
+  }
+}
+
+function getTestManifest(url, params, callback) {
+  var req = new XMLHttpRequest();
+  req.open("GET", url);
+  req.onload = function() {
+    if (req.readyState == 4) {
+      if (req.status == 200) {
+        parseTestManifest(JSON.parse(req.responseText), params, callback);
+      } else {
+        dump("TEST-ERROR: setup.js | error loading " + url + "\n");
+        callback({});
+      }
+    }
+  }
+  req.send();
+}
+
+// Test Filtering Code
+
+/*
+ Open the file referenced by runOnly|exclude and use that to compare against
+ testList
+ parameters:
+   filter = json object of runtests | excludetests
+   testList = array of test names to run
+   runOnly = use runtests vs excludetests in case both are defined
+ returns:
+   filtered version of testList
+*/
+function filterTests(filter, testList, runOnly) {
+
+  var filteredTests = [];
+  var removedTests = [];
+  var runtests = {};
+  var excludetests = {};
+
+  if (filter == null) {
+    return testList;
+  }
+
+  if ('runtests' in filter) {
+    runtests = filter.runtests;
+  }
+  if ('excludetests' in filter) {
+    excludetests = filter.excludetests;
+  }
+  if (!('runtests' in filter) && !('excludetests' in filter)) {
+    if (runOnly == 'true') {
+      runtests = filter;
+    } else {
+      excludetests = filter;
+    }
+  }
+
+  var testRoot = config.testRoot || "tests";
+  // Start with testList, and put everything that's in 'runtests' in
+  // filteredTests.
+  if (Object.keys(runtests).length) {
+    for (var i = 0; i < testList.length; i++) {
+      var testpath = testList[i];
+      var tmppath = testpath.replace(/^\//, '');
+      for (var f in runtests) {
+        // Remove leading /tests/ if exists
+        file = f.replace(/^\//, '')
+        file = file.replace(/^tests\//, '')
+
+        // Match directory or filename, testList has <testroot>/<path>
+        if (tmppath.match(testRoot + "/" + file) != null) {
+          filteredTests.push(testpath);
+          break;
+        }
+      }
+    }
+  } else {
+    filteredTests = testList.slice(0);
+  }
+
+  // Continue with filteredTests, and deselect everything that's in
+  // excludedtests.
+  if (!Object.keys(excludetests).length) {
+    return filteredTests;
+  }
+
+  var refilteredTests = [];
+  for (var i = 0; i < filteredTests.length; i++) {
+    var found = false;
+    var testpath = filteredTests[i];
+    var tmppath = testpath.replace(/^\//, '');
+    for (var f in excludetests) {
+      // Remove leading /tests/ if exists
+      file = f.replace(/^\//, '')
+      file = file.replace(/^tests\//, '')
+
+      // Match directory or filename, testList has <testroot>/<path>
+      if (tmppath.match(testRoot + "/" + file) != null) {
+        found = true;
+        break;
+      }
+    }
+    if (!found) {
+      refilteredTests.push(testpath);
+    }
+  }
+  return refilteredTests;
+}
+
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -265,31 +265,31 @@ class MochitestOptions(optparse.OptionPa
                 "Only available when running a single test. Default cap is 30 runs, "
                 "which can be overwritten with the --repeat parameter.",
           "default": False,
         }],
         [["--run-only-tests"],
         { "action": "store",
           "type": "string",
           "dest": "runOnlyTests",
-          "help": "JSON list of tests that we only want to run, cannot be specified with --exclude-tests. [DEPRECATED- please use --test-manifest]",
-          "default": None,
-        }],
-        [["--exclude-tests"],
-        { "action": "store",
-          "type": "string",
-          "dest": "excludeTests",
-          "help": "JSON list of tests that we want to not run, cannot be specified with --run-only-tests. [DEPRECATED- please use --test-manifest]",
+          "help": "JSON list of tests that we only want to run. [DEPRECATED- please use --test-manifest]",
           "default": None,
         }],
         [["--test-manifest"],
         { "action": "store",
           "type": "string",
           "dest": "testManifest",
-          "help": "JSON list of tests to specify 'runtests' and 'excludetests'.",
+          "help": "JSON list of tests to specify 'runtests'. Old format for mobile specific tests",
+          "default": None,
+        }],
+        [["--manifest"],
+        { "action": "store",
+          "type": "string",
+          "dest": "manifestFile",
+          "help": ".ini format of tests to run.",
           "default": None,
         }],
         [["--failure-file"],
         { "action": "store",
           "type": "string",
           "dest": "failureFile",
           "help": "Filename of the output file where we can store a .json list of failures to be run in the future with --run-only-tests.",
           "default": None,
@@ -316,16 +316,23 @@ class MochitestOptions(optparse.OptionPa
         [["--setpref"],
         { "action": "append",
           "type": "string",
           "default": [],
           "dest": "extraPrefs",
           "metavar": "PREF=VALUE",
           "help": "defines an extra user preference",
         }],
+        [["--build-info-json"],
+        { "action": "store",
+          "type": "string",
+          "default": None,
+          "dest": "mozInfo",
+          "help": "path to mozinfo.json to determine build time options",
+        }],
     ]
 
     def __init__(self, automation=None, **kwargs):
         self._automation = automation or Automation()
         optparse.OptionParser.__init__(self, **kwargs)
         defaults = {}
 
         # we want to pass down everything from self._automation.__all__
@@ -395,33 +402,28 @@ class MochitestOptions(optparse.OptionPa
             if not self._automation.IS_WIN32:
                 self.error("use-vmware-recording is only supported on Windows.")
             mochitest.vmwareHelperPath = os.path.join(
                 options.utilityPath, VMWARE_RECORDING_HELPER_BASENAME + ".dll")
             if not os.path.exists(mochitest.vmwareHelperPath):
                 self.error("%s not found, cannot automate VMware recording." %
                            mochitest.vmwareHelperPath)
 
-        if options.runOnlyTests != None and options.excludeTests != None:
-            self.error("We can only support --run-only-tests OR --exclude-tests, not both.  Please consider using --test-manifest instead.")
-
-        if options.testManifest != None and (options.runOnlyTests != None or options.excludeTests != None):
-            self.error("Please use --test-manifest only and not --run-only-tests or --exclude-tests.")
+        if options.testManifest and options.runOnlyTests:
+            self.error("Please use --test-manifest only and not --run-only-tests")
 
         if options.runOnlyTests:
             if not os.path.exists(os.path.abspath(options.runOnlyTests)):
-                self.error("unable to find --run-only-tests file '%s'" % options.runOnlyTests);
+                self.error("unable to find --run-only-tests file '%s'" % options.runOnlyTests)
+            options.runOnly = True
             options.testManifest = options.runOnlyTests
-            options.runOnly = True
+            options.runOnlyTests = None
 
-        if options.excludeTests:
-            if not os.path.exists(os.path.abspath(options.excludeTests)):
-                self.error("unable to find --exclude-tests file '%s'" % options.excludeTests);
-            options.testManifest = options.excludeTests
-            options.runOnly = False
+        if options.manifestFile and options.testManifest:
+            self.error("Unable to support both --manifest and --test-manifest/--run-only-tests at the same time")
 
         if options.webapprtContent and options.webapprtChrome:
             self.error("Only one of --webapprt-content and --webapprt-chrome may be given.")
 
         # Try to guess the testing modules directory.
         # This somewhat grotesque hack allows the buildbot machines to find the
         # modules directory without having to configure the buildbot hosts. This
         # code should never be executed in local runs because the build system
@@ -458,16 +460,26 @@ class MochitestOptions(optparse.OptionPa
                 self.error("%s not found, cannot launch immersive tests." %
                            mochitest.immersiveHelperPath)
 
         if options.runUntilFailure:
             if not os.path.isfile(os.path.join(mochitest.oldcwd, os.path.dirname(__file__), mochitest.getTestRoot(options), options.testPath)):
                 self.error("--run-until-failure can only be used together with --test-path specifying a single test.")
             if not options.repeat:
                 options.repeat = 29
+
+        if not options.mozInfo:
+            if build_obj:
+                options.mozInfo = os.path.join(build_obj.get_binary_path(), 'mozinfo.json')
+            else:
+                options.mozInfo = os.path.abspath('mozinfo.json')
+
+        if not os.path.isfile(options.mozInfo):
+            self.error("Unable to file build information file (mozinfo.json) at this location: %s" % options.mozInfo)
+
         return options
 
 
 class B2GOptions(MochitestOptions):
     b2g_options = [
         [["--b2gpath"],
         { "action": "store",
           "type": "string",
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -18,18 +18,20 @@ SCRIPT_DIR = os.path.abspath(os.path.rea
 sys.path.insert(0, SCRIPT_DIR);
 
 import shutil
 from urllib import quote_plus as encodeURIComponent
 import urllib2
 from automation import Automation
 from automationutils import getDebuggerInfo, isURL, processLeakLog
 from mochitest_options import MochitestOptions
+from manifestparser import TestManifest
+import mozinfo
+import json
 
-import mozinfo
 import mozlog
 
 log = mozlog.getLogger('Mochitest')
 
 
 #######################
 # HTTP SERVER SUPPORT #
 #######################
@@ -234,23 +236,47 @@ class MochitestUtilsMixin(object):
       if os.path.isfile(os.path.join(self.oldcwd, os.path.dirname(__file__), self.TEST_PATH, options.testPath)) and options.repeat > 0:
         self.urlOpts.append("testname=%s" % ("/").join([self.TEST_PATH, options.testPath]))
       if options.testManifest:
         self.urlOpts.append("testManifest=%s" % options.testManifest)
         if hasattr(options, 'runOnly') and options.runOnly:
           self.urlOpts.append("runOnly=true")
         else:
           self.urlOpts.append("runOnly=false")
+      if options.manifestFile:
+        self.urlOpts.append("manifestFile=%s" % options.manifestFile)
       if options.failureFile:
         self.urlOpts.append("failureFile=%s" % self.getFullPath(options.failureFile))
       if options.runSlower:
         self.urlOpts.append("runSlower=true")
 
   def buildTestPath(self, options):
-    """ Build the url path to the specific test harness and test file or directory """
+    """ Build the url path to the specific test harness and test file or directory
+        Build a manifest of tests to run and write out a json file for the harness to read
+    """
+    if options.manifestFile and os.path.isfile(options.manifestFile):
+      manifest = TestManifest(strict=False)
+      manifest.read(options.manifestFile)
+      # Bug 883858 - return all tests including disabled tests 
+      tests = manifest.active_tests(disabled=False, **mozinfo.info)
+      paths = []
+      for test in tests:
+        tp = test['path'].split(self.getTestRoot(options), 1)[1].strip('/')
+
+        # Filter out tests if we are using --test-path
+        if options.testPath and not tp.startswith(options.testPath):
+          continue
+
+        paths.append({'path': tp})
+
+      # Bug 883865 - add this functionality into manifestDestiny
+      with open('tests.json', 'w') as manifestFile:
+        manifestFile.write(json.dumps({'tests': paths}))
+      options.manifestFile = 'tests.json'
+
     testHost = "http://mochi.test:8888"
     testURL = ("/").join([testHost, self.TEST_PATH, options.testPath])
     if os.path.isfile(os.path.join(self.oldcwd, os.path.dirname(__file__), self.TEST_PATH, options.testPath)) and options.repeat > 0:
        testURL = ("/").join([testHost, self.PLAIN_LOOP_PATH])
     if options.chrome or options.a11y:
        testURL = ("/").join([testHost, self.CHROME_PATH])
     elif options.browserChrome:
       testURL = "about:blank"
--- a/testing/mochitest/server.js
+++ b/testing/mochitest/server.js
@@ -573,19 +573,23 @@ function regularListing(metadata, respon
 }
 
 /**
  * Produce a test harness page containing all the test cases
  * below it, recursively.
  */
 function testListing(metadata, response)
 {
-  var [links, count] = list(metadata.path,
-                            metadata.getProperty("directory"),
-                            true);
+  var links = {};
+  var count = 0;
+  if (metadata.queryString.indexOf('manifestFile') == -1) {
+    [links, count] = list(metadata.path,
+                          metadata.getProperty("directory"),
+                          true);
+  }
   var table_class = metadata.queryString.indexOf("hideResultsTable=1") > -1 ? "invisible": "";
 
   let testname = (metadata.queryString.indexOf("testname=") > -1)
                  ? metadata.queryString.match(/testname=([^&]+)/)[1]
                  : "";
 
   dumpn("count: " + count);
   var tests = testname
@@ -602,16 +606,18 @@ function testListing(metadata, response)
                  src: "/tests/SimpleTest/LogController.js"}),
         SCRIPT({type: "text/javascript",
                  src: "/tests/SimpleTest/TestRunner.js"}),
         SCRIPT({type: "text/javascript",
                  src: "/tests/SimpleTest/MozillaLogger.js"}),
         SCRIPT({type: "text/javascript",
                  src: "/chunkifyTests.js"}),
         SCRIPT({type: "text/javascript",
+                 src: "/manifestLibrary.js"}),
+        SCRIPT({type: "text/javascript",
                  src: "/tests/SimpleTest/setup.js"}),
         SCRIPT({type: "text/javascript"},
                "window.onload =  hookup; gTestList=" + tests + ";"
         )
       ),
       BODY(
         DIV({class: "container"},
           H2("--> ", A({href: "#", id: "runtests"}, "Run Tests"), " <--"),
--- a/testing/mochitest/tests/SimpleTest/setup.js
+++ b/testing/mochitest/tests/SimpleTest/setup.js
@@ -60,16 +60,29 @@ if (config.testRoot == "chrome" || confi
       config[p] = true;
     } else if (params[p] == 0) {
       config[p] = false;
     } else {
       config[p] = params[p];
     }
   }
   params = config;
+  params.baseurl = "chrome://mochitests/content";
+} else {
+  params.baseurl = "";
+}
+
+if (params.testRoot == "browser") {
+  params.testPrefix = "chrome://mochitests/content/browser/";
+} else if (params.testRoot == "chrome") {
+  params.testPrefix = "chrome://mochitests/content/chrome/";
+} else if (params.testRoot == "a11y") {
+  params.testPrefix = "chrome://mochitests/content/a11y/";
+} else {
+  params.testPrefix = "/tests/";
 }
 
 // set the per-test timeout if specified in the query string
 if (params.timeout) {
   TestRunner.timeout = parseInt(params.timeout) * 1000;
 }
 
 // log levels for console and logfile
@@ -108,24 +121,29 @@ if (!params.quiet) {
   TestRunner.logger.addListener("dumpListener", consoleLevel + "", dumpListener);
 }
 
 // A temporary hack for android 4.0 where Fennec utilizes the pandaboard so much it reboots
 if (params.runSlower) {
   TestRunner.runSlower = true;
 }
 
-
 var gTestList = [];
 var RunSet = {}
 RunSet.runall = function(e) {
   // Filter tests to include|exclude tests based on data in params.filter.
   // This allows for including or excluding tests from the gTestList
-  gTestList = filterTests(params.testManifest, params.runOnly);
+  if (params.testManifest) {
+    getTestManifest("http://mochi.test:8888/" + params.testManifest, params, function(filter) { gTestList = filterTests(filter, gTestList, params.runOnly); RunSet.runtests(); });
+  } else {
+    RunSet.runtests();
+  }
+}
 
+RunSet.runtests = function(e) {
   // Which tests we're going to run
   var my_tests = gTestList;
 
   if (params.totalChunks && params.thisChunk) {
     my_tests = chunkifyTests(my_tests, params.totalChunks, params.thisChunk, params.chunkByDir, TestRunner.logger);
   }
 
   if (params.shuffle) {
@@ -148,106 +166,16 @@ RunSet.reloadAndRunAll = function(e) {
     window.location.href = window.location.href;
   } else if (window.location.search) {
     window.location.href += "&autorun=1";
   } else {
     window.location.href += "?autorun=1";
   }  
 };
 
-// Test Filtering Code
-
-// Open the file referenced by runOnly|exclude and use that to compare against
-// gTestList.  Return a modified version of gTestList
-function filterTests(filterFile, runOnly) {
-  var filteredTests = [];
-  var removedTests = [];
-  var runtests = {};
-  var excludetests = {};
-
-  if (filterFile == null) {
-    return gTestList;
-  }
-
-  var datafile = "http://mochi.test:8888/" + filterFile;
-  var objXml = new XMLHttpRequest();
-  objXml.open("GET",datafile,false);
-  objXml.send(null);
-  try {
-    var filter = JSON.parse(objXml.responseText);
-  } catch (ex) {
-    dump("INFO | setup.js | error loading or parsing '" + datafile + "'\n");
-    return gTestList;
-  }
-
-  if ('runtests' in filter) {
-    runtests = filter.runtests;
-  }
-  if ('excludetests' in filter)
-    excludetests = filter.excludetests;
-  if (!('runtests' in filter) && !('excludetests' in filter)) {
-    if (runOnly == 'true') {
-      runtests = filter;
-    } else
-      excludetests = filter;
-  }
-
-  var testRoot = config.testRoot || "tests";
-  // Start with gTestList, and put everything that's in 'runtests' in
-  // filteredTests.
-  if (Object.keys(runtests).length) {
-    for (var i = 0; i < gTestList.length; i++) {
-      var test_path = gTestList[i];
-      var tmp_path = test_path.replace(/^\//, '');
-      for (var f in runtests) {
-        // Remove leading /tests/ if exists
-        file = f.replace(/^\//, '')
-        file = file.replace(/^tests\//, '')
-
-        // Match directory or filename, gTestList has <testroot>/<path>
-        if (tmp_path.match(testRoot + "/" + file) != null) {
-          filteredTests.push(test_path);
-          break;
-        }
-      }
-    }
-  }
-  else {
-    filteredTests = gTestList.slice(0);
-  }
-
-  // Continue with filteredTests, and deselect everything that's in
-  // excludedtests.
-  if (Object.keys(excludetests).length) {
-    var refilteredTests = [];
-    for (var i = 0; i < filteredTests.length; i++) {
-      var found = false;
-      var test_path = filteredTests[i];
-      var tmp_path = test_path.replace(/^\//, '');
-      for (var f in excludetests) {
-        // Remove leading /tests/ if exists
-        file = f.replace(/^\//, '')
-        file = file.replace(/^tests\//, '')
-
-        // Match directory or filename, gTestList has <testroot>/<path>
-        if (tmp_path.match(testRoot + "/" + file) != null) {
-          found = true;
-          break;
-        }
-      }
-      if (!found) {
-        refilteredTests.push(test_path);
-      }
-    }
-    filteredTests = refilteredTests;
-  }
-
-  return filteredTests;
-}
-
 // UI Stuff
 function toggleVisible(elem) {
     toggleElementClass("invisible", elem);
 }
 
 function makeVisible(elem) {
     removeElementClass(elem, "invisible");
 }
@@ -272,15 +200,32 @@ function toggleNonTests (e) {
     $("toggleNonTests").innerHTML = "Hide Non-Tests";
   } else {
     $("toggleNonTests").innerHTML = "Show Non-Tests";
   }
 }
 
 // hook up our buttons
 function hookup() {
+  if (params.manifestFile) {
+    getTestManifest("http://mochi.test:8888/" + params.manifestFile, params, hookupTests);
+  } else {
+    hookupTests(gTestList);
+  }
+}
+
+function hookupTests(testList) {
+  if (testList.length > 0) {
+    gTestList = testList;
+  } else {
+    gTestList = [];
+    for (var obj in testList) {
+        gTestList.push(obj);
+    }
+  }
+
   document.getElementById('runtests').onclick = RunSet.reloadAndRunAll;
   document.getElementById('toggleNonTests').onclick = toggleNonTests; 
   // run automatically if autorun specified
   if (params.autorun) {
     RunSet.runall();
   }
 }
--- a/testing/testsuite-targets.mk
+++ b/testing/testsuite-targets.mk
@@ -39,25 +39,27 @@ RUN_MOCHITEST_B2G_DESKTOP = \
 
 RUN_MOCHITEST = \
   rm -f ./$@.log && \
   $(PYTHON) _tests/testing/mochitest/runtests.py --autorun --close-when-done \
     --console-level=INFO --log-file=./$@.log --file-level=INFO \
     --failure-file=$(call core_abspath,_tests/testing/mochitest/makefailures.json) \
     --testing-modules-dir=$(call core_abspath,_tests/modules) \
     --extra-profile-file=$(DIST)/plugins \
+    --build-info-json=$(call core_abspath,$(DEPTH)/mozinfo.json) \
     $(SYMBOLS_PATH) $(TEST_PATH_ARG) $(EXTRA_TEST_ARGS)
 
 RERUN_MOCHITEST = \
   rm -f ./$@.log && \
   $(PYTHON) _tests/testing/mochitest/runtests.py --autorun --close-when-done \
     --console-level=INFO --log-file=./$@.log --file-level=INFO \
     --run-only-tests=makefailures.json \
     --testing-modules-dir=$(call core_abspath,_tests/modules) \
     --extra-profile-file=$(DIST)/plugins \
+    --build-info-json=$(call core_abspath,$(DEPTH)/mozinfo.json) \
     $(SYMBOLS_PATH) $(TEST_PATH_ARG) $(EXTRA_TEST_ARGS)
 
 RUN_MOCHITEST_REMOTE = \
   rm -f ./$@.log && \
   $(PYTHON) _tests/testing/mochitest/runtestsremote.py --autorun --close-when-done \
     --console-level=INFO --log-file=./$@.log --file-level=INFO $(DM_FLAGS) --dm_trans=$(DM_TRANS) \
     --app=$(TEST_PACKAGE_NAME) --deviceIP=${TEST_DEVICE} --xre-path=${MOZ_HOST_BIN} \
     --testing-modules-dir=$(call core_abspath,_tests/modules) --httpd-path=. \