Bug 872229 - Add an add_task API for mochitest. r=ted

This commit is contained in:
David Rajchenbach-Teller 2013-06-04 15:35:43 +02:00
Родитель f535093ab0
Коммит 8f97970e9c
5 изменённых файлов: 171 добавлений и 5 удалений

Просмотреть файл

@ -7,10 +7,9 @@
let tmp = {};
Cu.import("resource://gre/modules/osfile.jsm", tmp);
Cu.import("resource://gre/modules/Task.jsm", tmp);
Cu.import("resource:///modules/sessionstore/_SessionFile.jsm", tmp);
const {OS, Task, _SessionFile} = tmp;
const {OS, _SessionFile} = tmp;
const PREF_SS_INTERVAL = "browser.sessionstore.interval";
// Full paths for sessionstore.js and sessionstore.bak.

Просмотреть файл

@ -73,10 +73,14 @@ function Tester(aTests, aDumper, aCallback) {
this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/SimpleTest.js", simpleTestScope);
this._scriptLoader.loadSubScript("chrome://mochikit/content/chrome-harness.js", simpleTestScope);
this.SimpleTest = simpleTestScope.SimpleTest;
this.Task = Components.utils.import("resource://gre/modules/Task.jsm", null).Task;
this.Promise = Components.utils.import("resource://gre/modules/commonjs/sdk/core/promise.js", null).Promise;
}
Tester.prototype = {
EventUtils: {},
SimpleTest: {},
Task: null,
Promise: null,
repeat: 0,
runUntilFailure: false,
@ -398,6 +402,8 @@ Tester.prototype = {
this.currentTest.scope.EventUtils = this.EventUtils;
this.currentTest.scope.SimpleTest = this.SimpleTest;
this.currentTest.scope.gTestPath = this.currentTest.path;
this.currentTest.scope.Task = this.Task;
this.currentTest.scope.Promise = this.Promise;
// Override SimpleTest methods with ours.
["ok", "is", "isnot", "ise", "todo", "todo_is", "todo_isnot", "info"].forEach(function(m) {
@ -431,9 +437,34 @@ Tester.prototype = {
// Run the test
this.lastStartTime = Date.now();
if ("generatorTest" in this.currentTest.scope) {
if ("test" in this.currentTest.scope)
if (this.currentTest.scope.__tasks) {
// This test consists of tasks, added via the `add_task()` API.
if ("test" in this.currentTest.scope) {
throw "Cannot run both a add_task test and a normal test at the same time.";
}
let testScope = this.currentTest.scope;
let currentTest = this.currentTest;
this.Task.spawn(function() {
let task;
while ((task = this.__tasks.shift())) {
this.SimpleTest.info("Entering test " + task.name);
try {
yield task();
} catch (ex) {
let isExpected = !!this.SimpleTest.isExpectingUncaughtException();
let stack = (typeof ex == "object" && "stack" in ex)?ex.stack:null;
let name = "Uncaught exception";
let result = new testResult(isExpected, name, ex, false, stack);
currentTest.addResult(result);
}
this.SimpleTest.info("Leaving test " + task.name);
}
this.finish();
}.bind(testScope));
} else if ("generatorTest" in this.currentTest.scope) {
if ("test" in this.currentTest.scope) {
throw "Cannot run both a generator test and a normal test at the same time.";
}
// This test is a generator. It will not finish immediately.
this.currentTest.scope.waitForExplicitFinish();
@ -444,7 +475,7 @@ Tester.prototype = {
this.currentTest.scope.test();
}
} catch (ex) {
var isExpected = !!this.SimpleTest.isExpectingUncaughtException();
let isExpected = !!this.SimpleTest.isExpectingUncaughtException();
if (!this.SimpleTest.isIgnoringAllUncaughtExceptions()) {
this.currentTest.addResult(new testResult(isExpected, "Exception thrown", ex, false));
this.SimpleTest.expectUncaughtException(false);
@ -668,12 +699,58 @@ function testScope(aTester, aTest) {
testScope.prototype = {
__done: true,
__generator: null,
__tasks: null,
__waitTimer: null,
__cleanupFunctions: [],
__timeoutFactor: 1,
EventUtils: {},
SimpleTest: {},
Task: null,
Promise: null,
/**
* Add a test function which is a Task function.
*
* Task functions are functions fed into Task.jsm's Task.spawn(). They are
* generators that emit promises.
*
* If an exception is thrown, an assertion fails, or if a rejected
* promise is yielded, the test function aborts immediately and the test is
* reported as a failure. Execution continues with the next test function.
*
* To trigger premature (but successful) termination of the function, simply
* return or throw a Task.Result instance.
*
* Example usage:
*
* add_task(function test() {
* let result = yield Promise.resolve(true);
*
* ok(result);
*
* let secondary = yield someFunctionThatReturnsAPromise(result);
* is(secondary, "expected value");
* });
*
* add_task(function test_early_return() {
* let result = yield somethingThatReturnsAPromise();
*
* if (!result) {
* // Test is ended immediately, with success.
* return;
* }
*
* is(result, "foo");
* });
*/
add_task: function(aFunction) {
if (!this.__tasks) {
this.waitForExplicitFinish();
this.__tasks = [];
}
this.__tasks.push(aFunction.bind(this));
},
destroy: function test_destroy() {
for (let prop in this)

Просмотреть файл

@ -14,6 +14,7 @@ MOCHITEST_BROWSER_FILES = \
head.js \
browser_head.js \
browser_pass.js \
browser_add_task.js \
browser_async.js \
browser_privileges.js \
browser_popupNode.js \
@ -25,6 +26,7 @@ MOCHITEST_BROWSER_FILES = \
# Disabled, these are only good for testing the harness' failure reporting
# browser_zz_fail_openwindow.js \
# browser_fail.js \
# browser_fail_add_task.js \
# browser_fail_async_throw.js \
# browser_fail_fp.js \
# browser_fail_pf.js \

Просмотреть файл

@ -0,0 +1,31 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let test1Complete = false;
let test2Complete = false;
function executeWithTimeout() {
let deferred = Promise.defer();
executeSoon(function() {
ok(true, "we get here after a timeout");
deferred.resolve();
});
return deferred.promise;
}
add_task(function asyncTest_no1() {
yield executeWithTimeout();
test1Complete = true;
});
add_task(function asyncTest_no2() {
yield executeWithTimeout();
test2Complete = true;
});
add_task(function() {
ok(test1Complete, "We have been through test 1");
ok(test2Complete, "We have been through test 2");
});

Просмотреть файл

@ -0,0 +1,57 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// This test is designed to fail.
// It ensures that throwing an asynchronous error from add_task will
// fail the test.
let passedTests = 0;
function rejectWithTimeout(error = undefined) {
let deferred = Promise.defer();
executeSoon(function() {
ok(true, "we get here after a timeout");
deferred.reject(error);
});
return deferred.promise;
}
add_task(function failWithoutError() {
try {
yield rejectWithTimeout();
} finally {
++passedTests;
}
});
add_task(function failWithString() {
try {
yield rejectWithTimeout("Meaningless error");
} finally {
++passedTests;
}
});
add_task(function failWithoutInt() {
try {
yield rejectWithTimeout(42);
} finally {
++passedTests;
}
});
// This one should display a stack trace
add_task(function failWithError() {
try {
yield rejectWithTimeout(new Error("This is an error"));
} finally {
++passedTests;
}
});
add_task(function done() {
is(passedTests, 4, "Passed all tests");
});