diff --git a/testing/mochitest/runtests.py.in b/testing/mochitest/runtests.py.in index bc1607b794bd..fdd63cd919d4 100644 --- a/testing/mochitest/runtests.py.in +++ b/testing/mochitest/runtests.py.in @@ -149,6 +149,27 @@ class MochitestOptions(optparse.OptionParser): help = "start running tests when the application starts") defaults["autorun"] = False + self.add_option("--total-chunks", + type = "int", dest = "totalChunks", + help = "how many chunks to split the tests up into") + defaults["totalChunks"] = None + + self.add_option("--this-chunk", + type = "int", dest = "thisChunk", + help = "which chunk to run") + defaults["thisChunk"] = None + + self.add_option("--chunk-by-dir", + type = "int", dest = "chunkByDir", + help = "group tests together in the same chunk that are in the same top chunkByDir directories") + defaults["chunkByDir"] = 0 + + self.add_option("--shuffle", + dest = "shuffle", + action = "store_true", + help = "randomize test order") + defaults["shuffle"] = False + LOG_LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR", "FATAL") LEVEL_STRING = ", ".join(LOG_LEVELS) @@ -339,6 +360,13 @@ def main(): parser = MochitestOptions() options, args = parser.parse_args() + if options.totalChunks is not None and options.thisChunk is None: + parser.error("thisChunk must be specified when totalChunks is specified") + + if options.totalChunks: + if not 1 <= options.thisChunk <= options.totalChunks: + parser.error("thisChunk must be between 1 and totalChunks") + if options.xrePath is None: # default xrePath to the app path if not provided # but only if an app path was explicitly provided @@ -426,6 +454,8 @@ Are you executing $objdir/_tests/testing/mochitest/runtests.py?""" # autorun -- kick off tests automatically # closeWhenDone -- runs quit.js after tests # logFile -- logs test run to an absolute path + # totalChunks -- how many chunks to split tests into + # thisChunk -- which chunk to run # # consoleLevel, fileLevel: set the logging level of the console and @@ -460,6 +490,13 @@ Are you executing $objdir/_tests/testing/mochitest/runtests.py?""" urlOpts.append("fileLevel=" + encodeURIComponent(options.fileLevel)) if options.consoleLevel: urlOpts.append("consoleLevel=" + encodeURIComponent(options.consoleLevel)) + if options.totalChunks: + urlOpts.append("totalChunks=%d" % options.totalChunks) + urlOpts.append("thisChunk=%d" % options.thisChunk) + if options.chunkByDir: + urlOpts.append("chunkByDir=%d" % options.chunkByDir) + if options.shuffle: + urlOpts.append("shuffle=1") if len(urlOpts) > 0: testURL += "?" + "&".join(urlOpts) diff --git a/testing/mochitest/tests/SimpleTest/setup.js b/testing/mochitest/tests/SimpleTest/setup.js index ba2c517f98be..b8a4ecfdfc1c 100644 --- a/testing/mochitest/tests/SimpleTest/setup.js +++ b/testing/mochitest/tests/SimpleTest/setup.js @@ -68,7 +68,68 @@ if (!params.quiet) { var gTestList = []; var RunSet = {} RunSet.runall = function(e) { - TestRunner.runTests(gTestList); + // Which tests we're going to run + var my_tests = gTestList; + + if (params.totalChunks && params.thisChunk) { + var total_chunks = parseInt(params.totalChunks); + // this_chunk is in the range [1,total_chunks] + var this_chunk = parseInt(params.thisChunk); + + // We want to split the tests up into chunks according to which directory + // they're in + if (params.chunkByDir) { + var chunkByDir = parseInt(params.chunkByDir); + var tests_by_dir = {}; + var test_dirs = [] + for (var i = 0; i < gTestList.length; ++i) { + var test_path = gTestList[i]; + if (test_path[0] == '/') { + test_path = test_path.substr(1); + } + var dir = test_path.split("/"); + // We want the first chunkByDir+1 components, or everything but the + // last component, whichever is less. + // we add 1 to chunkByDir since 'tests' is always part of the path, and + // want to ignore the last component since it's the test filename. + dir = dir.slice(0, Math.min(chunkByDir+1, dir.length-1)); + // reconstruct a directory name + dir = dir.join("/"); + if (!(dir in tests_by_dir)) { + tests_by_dir[dir] = [gTestList[i]]; + test_dirs.push(dir); + } else { + tests_by_dir[dir].push(gTestList[i]); + } + } + var tests_per_chunk = test_dirs.length / total_chunks; + var start = Math.round((this_chunk-1) * tests_per_chunk); + var end = Math.round(this_chunk * tests_per_chunk); + my_tests = []; + var dirs = [] + for (var i = start; i < end; ++i) { + var dir = test_dirs[i]; + dirs.push(dir); + my_tests = my_tests.concat(tests_by_dir[dir]); + } + TestRunner.logger.log("Running tests in " + dirs.join(", ")); + } else { + var tests_per_chunk = gTestList.length / total_chunks; + var start = Math.round((this_chunk-1) * tests_per_chunk); + var end = Math.round(this_chunk * tests_per_chunk); + my_tests = gTestList.slice(start, end); + TestRunner.logger.log("Running tests " + (start+1) + "-" + end + "/" + gTestList.length); + } + } + if (params.shuffle) { + for (var i = my_tests.length-1; i > 0; --i) { + var j = Math.floor(Math.random() * i); + var tmp = my_tests[j]; + my_tests[j] = my_tests[i]; + my_tests[i] = tmp; + } + } + TestRunner.runTests(my_tests); } RunSet.reloadAndRunAll = function(e) { e.preventDefault();