diff --git a/testing/mochitest/Makefile.in b/testing/mochitest/Makefile.in
index 7bb3ba48312c..86b73957d85d 100644
--- a/testing/mochitest/Makefile.in
+++ b/testing/mochitest/Makefile.in
@@ -100,6 +100,7 @@ _SERV_FILES = \
$(topsrcdir)/netwerk/test/httpserver/httpd.js \
mozprefs.js \
pywebsocket_wrapper.py \
+ plain-loop.html \
$(NULL)
_PYWEBSOCKET_FILES = \
diff --git a/testing/mochitest/browser-test.js b/testing/mochitest/browser-test.js
index 6ccc5d0b99ff..d305825eef17 100644
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -58,6 +58,7 @@ Tester.prototype = {
EventUtils: {},
SimpleTest: {},
+ loops: 0,
checker: null,
currentTestIndex: -1,
lastStartTime: null,
@@ -69,6 +70,10 @@ Tester.prototype = {
},
start: function Tester_start() {
+ //if testOnLoad was not called, then gConfig is not defined
+ if(!gConfig)
+ gConfig = readConfig();
+ this.loops = gConfig.loops;
this.dumper.dump("*** Start BrowserChrome Test Results ***\n");
this._cs.registerListener(this);
@@ -124,33 +129,40 @@ Tester.prototype = {
},
finish: function Tester_finish(aSkipSummary) {
- this._cs.unregisterListener(this);
-
- this.dumper.dump("\nINFO TEST-START | Shutdown\n");
- if (this.tests.length) {
- this.dumper.dump("Browser Chrome Test Summary\n");
-
- function sum(a,b) a+b;
- var passCount = this.tests.map(function (f) f.passCount).reduce(sum);
- var failCount = this.tests.map(function (f) f.failCount).reduce(sum);
- var todoCount = this.tests.map(function (f) f.todoCount).reduce(sum);
-
- this.dumper.dump("\tPassed: " + passCount + "\n" +
- "\tFailed: " + failCount + "\n" +
- "\tTodo: " + todoCount + "\n");
- } else {
- this.dumper.dump("TEST-UNEXPECTED-FAIL | (browser-test.js) | " +
- "No tests to run. Did you pass an invalid --test-path?");
+ if(this.loops > 0){
+ --this.loops;
+ this.currentTestIndex = -1;
+ this.nextTest();
+ }
+ else{
+ this._cs.unregisterListener(this);
+
+ this.dumper.dump("\nINFO TEST-START | Shutdown\n");
+ if (this.tests.length) {
+ this.dumper.dump("Browser Chrome Test Summary\n");
+
+ function sum(a,b) a+b;
+ var passCount = this.tests.map(function (f) f.passCount).reduce(sum);
+ var failCount = this.tests.map(function (f) f.failCount).reduce(sum);
+ var todoCount = this.tests.map(function (f) f.todoCount).reduce(sum);
+
+ this.dumper.dump("\tPassed: " + passCount + "\n" +
+ "\tFailed: " + failCount + "\n" +
+ "\tTodo: " + todoCount + "\n");
+ } else {
+ this.dumper.dump("TEST-UNEXPECTED-FAIL | (browser-test.js) | " +
+ "No tests to run. Did you pass an invalid --test-path?");
+ }
+
+ this.dumper.dump("\n*** End BrowserChrome Test Results ***\n");
+
+ this.dumper.done();
+
+ // Tests complete, notify the callback and return
+ this.callback(this.tests);
+ this.callback = null;
+ this.tests = null;
}
-
- this.dumper.dump("\n*** End BrowserChrome Test Results ***\n");
-
- this.dumper.done();
-
- // Tests complete, notify the callback and return
- this.callback(this.tests);
- this.callback = null;
- this.tests = null;
},
observe: function Tester_observe(aConsoleMessage) {
diff --git a/testing/mochitest/harness-overlay.xul b/testing/mochitest/harness-overlay.xul
index 7683fda13825..5df9c4af0686 100644
--- a/testing/mochitest/harness-overlay.xul
+++ b/testing/mochitest/harness-overlay.xul
@@ -32,7 +32,7 @@ function loadTests()
{
window.removeEventListener("load", loadTests, false);
[links, singleTestPath] = getTestList();
-
+
// load server.js in so we can share template functions
var scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
getService(Ci.mozIJSSubScriptLoader);
@@ -49,10 +49,13 @@ function loadTests()
}
gTestList = eval(srvScope.jsonArrayOfTestFiles(links));
populate();
- hookup();
- if (singleTestPath)
- window.location.href = singleTestPath;
+ if (singleTestPath) {
+ TestRunner.loopTest(singleTestPath);
+ } else {
+ hookup();
+ }
+
}
window.addEventListener("load", loadTests, false);
@@ -63,6 +66,7 @@ function loadTests()
+
Based on the MochiKit unit tests.
@@ -98,6 +102,12 @@ function loadTests()
+
+
diff --git a/testing/mochitest/plain-loop.html b/testing/mochitest/plain-loop.html
new file mode 100644
index 000000000000..1e97eae52261
--- /dev/null
+++ b/testing/mochitest/plain-loop.html
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Based on the MochiKit unit tests.
+
+
+
Status
+ Passed: 0
+ Failed: 0
+ Todo: 0
+
+
+
+ Currently Executing: _
+
+
+
+
+
+
+
+
+
+
+
+
+ Passed |
+ Failed |
+ Todo |
+ Test Files |
+
+
+ 0 |
+ 0 |
+ 0 |
+ |
+
+
+
+
+
+
+
+
+
diff --git a/testing/mochitest/runtests.py b/testing/mochitest/runtests.py
index c5ac057fddfc..4364c4be1237 100644
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -232,6 +232,13 @@ class MochitestOptions(optparse.OptionParser):
"inside a VMware Workstation 7.0 or later VM")
defaults["vmwareRecording"] = False
+ self.add_option("--loops",
+ action = "store", type = "int",
+ dest = "loops", metavar = "LOOPS",
+ help = "repeats the test or set of tests the given number of times "
+ "without restarting the browser (given number > 0)")
+ defaults["loops"] = 0
+
# -h, --help are automatically handled by OptionParser
self.set_defaults(**defaults)
@@ -405,6 +412,7 @@ class Mochitest(object):
# Path to the test script on the server
TEST_PATH = "/tests/"
CHROME_PATH = "/redirect.html";
+ PLAIN_LOOP_PATH = "/plain-loop.html";
urlOpts = []
runSSLTunnel = True
vmwareHelper = None
@@ -433,6 +441,8 @@ class Mochitest(object):
""" Build the url path to the specific test harness and test file or directory """
testHost = "http://mochi.test:8888"
testURL = testHost + self.TEST_PATH + options.testPath
+ if os.path.isfile(self.oldcwd + self.TEST_PATH + options.testPath) and options.loops > 0:
+ testURL = testHost + self.PLAIN_LOOP_PATH
if options.chrome or options.a11y:
testURL = testHost + self.CHROME_PATH
elif options.browserChrome:
@@ -537,6 +547,7 @@ class Mochitest(object):
totalChunks -- how many chunks to split tests into
thisChunk -- which chunk to run
timeout -- per-test timeout in seconds
+ loops -- How many times to run the test
"""
# allow relative paths for logFile
@@ -563,6 +574,10 @@ class Mochitest(object):
self.urlOpts.append("chunkByDir=%d" % options.chunkByDir)
if options.shuffle:
self.urlOpts.append("shuffle=1")
+ if options.loops:
+ self.urlOpts.append("loops=%d" % options.loops)
+ if os.path.isfile(self.oldcwd + self.TEST_PATH + options.testPath) and options.loops > 0:
+ self.urlOpts.append("testname=%s" % (self.TEST_PATH + options.testPath))
def cleanup(self, manifest, options):
""" remove temporary files and profile """
diff --git a/testing/mochitest/server.js b/testing/mochitest/server.js
index a21e2cb87a93..1131befd9e6b 100644
--- a/testing/mochitest/server.js
+++ b/testing/mochitest/server.js
@@ -662,6 +662,11 @@ function testListing(metadata, response)
TR(TD("Passed"), TD("Failed"), TD("Todo"), TD("Test Files")),
linksToTableRows(links, 0)
),
+
+ BR(),
+ TABLE({cellpadding: 0, cellspacing: 0, border: 1, bordercolor: "red", id: "fail-table"}
+ ),
+
DIV({class: "clear"})
)
)
diff --git a/testing/mochitest/tests/SimpleTest/TestRunner.js b/testing/mochitest/tests/SimpleTest/TestRunner.js
index c15d0f88f243..8b6228181bc1 100644
--- a/testing/mochitest/tests/SimpleTest/TestRunner.js
+++ b/testing/mochitest/tests/SimpleTest/TestRunner.js
@@ -37,6 +37,7 @@ var TestRunner = {};
TestRunner.logEnabled = false;
TestRunner._currentTest = 0;
TestRunner.currentTestURL = "";
+TestRunner.originalTestURL = "";
TestRunner._urls = [];
TestRunner.timeout = 5 * 60 * 1000; // 5 minutes.
@@ -85,6 +86,12 @@ TestRunner.requestLongerTimeout = function(factor) {
TestRunner._timeoutFactor = factor;
}
+/**
+ * This is used to loop tests
+**/
+TestRunner.loops = 0;
+TestRunner._currentLoop = 0;
+
/**
* This function is called after generating the summary.
**/
@@ -124,7 +131,6 @@ TestRunner._toggle = function(el) {
}
};
-
/**
* Creates the iframe that contains a test
**/
@@ -163,6 +169,7 @@ TestRunner._makeIframe = function (url, retry) {
**/
TestRunner.runTests = function (/*url...*/) {
TestRunner.log("SimpleTest START");
+ TestRunner.originalTestURL = $("current-test").innerHTML;
SpecialPowers.registerProcessCrashObservers();
@@ -174,6 +181,49 @@ TestRunner.runTests = function (/*url...*/) {
TestRunner.runNextTest();
};
+/**
+ * Used for running a set of tests in a loop for debugging purposes
+ * Takes an array of URLs
+**/
+TestRunner.resetTests = function(listURLs) {
+ TestRunner._currentTest = 0;
+ // Reset our "Current-test" line - functionality depends on it
+ $("current-test").innerHTML = TestRunner.originalTestURL;
+ if (TestRunner.logEnabled)
+ TestRunner.log("SimpleTest START Loop " + TestRunner._currentLoop);
+
+ TestRunner._urls = listURLs;
+ $('testframe').src="";
+ TestRunner._checkForHangs();
+ window.focus();
+ $('testframe').focus();
+ TestRunner.runNextTest();
+}
+
+/*
+ * Used to run a single test in a loop and update the UI with the results
+ */
+TestRunner.loopTest = function(testPath){
+ var numLoops = TestRunner.loops;
+ while(numLoops >= 0){
+ //must set the following line so that TestHarness.updateUI finds the right div to update
+ $("current-test-path").innerHTML = testPath;
+ function checkComplete() {
+ var testWindow = window.open(testPath, 'test window');
+ if (testWindow.document.readyState == "complete") {
+ TestRunner.currentTestURL = testPath;
+ TestRunner.updateUI(testWindow.SimpleTest._tests);
+ testWindow.close();
+ } else {
+ setTimeout(checkComplete, 1000);
+ }
+ }
+ checkComplete();
+ numLoops--;
+ }
+}
+
+/**
/**
* Run the next test. If no test remains, calls onComplete().
**/
@@ -216,11 +266,27 @@ TestRunner.runNextTest = function() {
TestRunner.log("Passed: " + $("pass-count").innerHTML);
TestRunner.log("Failed: " + $("fail-count").innerHTML);
TestRunner.log("Todo: " + $("todo-count").innerHTML);
- TestRunner.log("SimpleTest FINISHED");
+ // If we are looping, don't send this cause it closes the log file
+ if (TestRunner.loops == 0)
+ TestRunner.log("SimpleTest FINISHED");
- if (TestRunner.onComplete) {
+ if (TestRunner.loops == 0 && TestRunner.onComplete) {
+ TestRunner.onComplete();
+ }
+
+ if (TestRunner._currentLoop < TestRunner.loops){
+ TestRunner._currentLoop++;
+ TestRunner.resetTests(TestRunner._urls);
+ } else {
+ // Loops are finished
+ if (TestRunner.logEnabled) {
+ TestRunner.log("TEST-INFO | Ran " + TestRunner._currentLoop + " Loops");
+ TestRunner.log("SimpleTest FINISHED");
+ }
+
+ if (TestRunner.onComplete)
TestRunner.onComplete();
- }
+ }
}
};
@@ -292,6 +358,47 @@ TestRunner.countResults = function(tests) {
return {"OK": nOK, "notOK": nNotOK, "todo": nTodo};
}
+/**
+ * Print out table of any error messages found during looped run
+ */
+TestRunner.displayLoopErrors = function(tableName, tests) {
+ if(TestRunner.countResults(tests).notOK >0){
+ var table = $(tableName);
+ var curtest;
+ if (table.rows.length == 0) {
+ //if table headers are not yet generated, make them
+ var row = table.insertRow(table.rows.length);
+ var cell = row.insertCell(0);
+ var textNode = document.createTextNode("Test File Name:");
+ cell.appendChild(textNode);
+ cell = row.insertCell(1);
+ textNode = document.createTextNode("Test:");
+ cell.appendChild(textNode);
+ cell = row.insertCell(2);
+ textNode = document.createTextNode("Error message:");
+ cell.appendChild(textNode);
+ }
+
+ //find the broken test
+ for (var testnum in tests){
+ curtest = tests[testnum];
+ if( !((curtest.todo && !curtest.result) || (curtest.result && !curtest.todo)) ){
+ //this is a failed test or the result of todo test. Display the related message
+ row = table.insertRow(table.rows.length);
+ cell = row.insertCell(0);
+ textNode = document.createTextNode(TestRunner.currentTestURL);
+ cell.appendChild(textNode);
+ cell = row.insertCell(1);
+ textNode = document.createTextNode(curtest.name);
+ cell.appendChild(textNode);
+ cell = row.insertCell(2);
+ textNode = document.createTextNode((curtest.diag ? curtest.diag : "" ));
+ cell.appendChild(textNode);
+ }
+ }
+ }
+}
+
TestRunner.updateUI = function(tests) {
var results = TestRunner.countResults(tests);
var passCount = parseInt($("pass-count").innerHTML) + results.OK;
@@ -319,9 +426,14 @@ TestRunner.updateUI = function(tests) {
var row = $(trID);
var tds = row.getElementsByTagName("td");
tds[0].style.backgroundColor = "#0d0";
- tds[0].innerHTML = results.OK;
+ tds[0].innerHTML = parseInt(tds[0].innerHTML) + parseInt(results.OK);
tds[1].style.backgroundColor = results.notOK > 0 ? "red" : "#0d0";
- tds[1].innerHTML = results.notOK;
+ tds[1].innerHTML = parseInt(tds[1].innerHTML) + parseInt(results.notOK);
tds[2].style.backgroundColor = results.todo > 0 ? "orange" : "#0d0";
- tds[2].innerHTML = results.todo;
+ tds[2].innerHTML = parseInt(tds[2].innerHTML) + parseInt(results.todo);
+
+ //if we ran in a loop, display any found errors
+ if(TestRunner.loops > 0){
+ TestRunner.displayLoopErrors('fail-table', tests);
+ }
}
diff --git a/testing/mochitest/tests/SimpleTest/setup.js b/testing/mochitest/tests/SimpleTest/setup.js
index 9fa7dc855c84..9e27ef7dfb4f 100644
--- a/testing/mochitest/tests/SimpleTest/setup.js
+++ b/testing/mochitest/tests/SimpleTest/setup.js
@@ -69,6 +69,11 @@ if (params.timeout) {
var fileLevel = params.fileLevel || null;
var consoleLevel = params.consoleLevel || null;
+// loop tells us how many times to run the tests
+if (params.loops) {
+ TestRunner.loops = params.loops;
+}
+
// closeWhenDone tells us to call quit.js when complete
if (params.closeWhenDone) {
TestRunner.onComplete = goQuitApplication;
@@ -154,6 +159,7 @@ RunSet.runall = function(e) {
}
TestRunner.runTests(my_tests);
}
+
RunSet.reloadAndRunAll = function(e) {
e.preventDefault();
//window.location.hash = "";
@@ -165,8 +171,7 @@ RunSet.reloadAndRunAll = function(e) {
window.location.href += "&autorun=1";
} else {
window.location.href += "?autorun=1";
- }
-
+ }
};
// UI Stuff
@@ -205,7 +210,7 @@ function toggleNonTests (e) {
function hookup() {
connect("runtests", "onclick", RunSet, "reloadAndRunAll");
connect("toggleNonTests", "onclick", toggleNonTests);
- // run automatically if
+ // run automatically if autorun specified
if (params.autorun) {
RunSet.runall();
}