mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-04 13:07:52 +00:00
Bug 783987 - Test suite without hostile promises. r=Mossop
This commit is contained in:
parent
9a8375e98e
commit
e6a8c6b79b
@ -125,6 +125,7 @@ skip-if = os == "win" || os == "mac" || os == "os2"
|
||||
[include:netwerk/test/unit_ipc/xpcshell.ini]
|
||||
[include:netwerk/cookie/test/unit_ipc/xpcshell.ini]
|
||||
[include:toolkit/components/contentprefs/tests/unit_ipc/xpcshell.ini]
|
||||
[include:toolkit/addon-sdk/test/unit/xpcshell.ini]
|
||||
[include:uriloader/exthandler/tests/unit_ipc/xpcshell.ini]
|
||||
|
||||
[include:modules/libmar/tests/unit/xpcshell.ini]
|
||||
|
@ -20,4 +20,6 @@ EXTRA_JS_MODULES := \
|
||||
loader.js \
|
||||
$(NULL)
|
||||
|
||||
TEST_DIRS += test
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
15
toolkit/addon-sdk/test/Makefile.in
Normal file
15
toolkit/addon-sdk/test/Makefile.in
Normal file
@ -0,0 +1,15 @@
|
||||
# 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/.
|
||||
|
||||
DEPTH = @DEPTH@
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = @relativesrcdir@
|
||||
|
||||
MODULE = test_addon_sdk
|
||||
XPCSHELL_TESTS = unit
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
74
toolkit/addon-sdk/test/unit/head.js
Normal file
74
toolkit/addon-sdk/test/unit/head.js
Normal file
@ -0,0 +1,74 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
|
||||
Components.utils.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
|
||||
let run_promise_tests = function run_promise_tests(tests, cb) {
|
||||
let timer = Components.classes["@mozilla.org/timer;1"]
|
||||
.createInstance(Components.interfaces.nsITimer);
|
||||
let loop = function loop(index) {
|
||||
if (index >= tests.length) {
|
||||
if (cb) {
|
||||
cb.call();
|
||||
}
|
||||
return;
|
||||
}
|
||||
do_print("Launching test " + (index + 1) + "/" + tests.length);
|
||||
let test = tests[index];
|
||||
// Execute from an empty stack
|
||||
let next = function next() {
|
||||
do_print("Test " + (index + 1) + "/" + tests.length + " complete");
|
||||
do_execute_soon(function() {
|
||||
loop(index + 1);
|
||||
});
|
||||
};
|
||||
let result = test();
|
||||
result.then(next, next);
|
||||
};
|
||||
return loop(0);
|
||||
};
|
||||
|
||||
let make_promise_test = function(test) {
|
||||
return function runtest() {
|
||||
do_print("Test starting: " + test);
|
||||
try {
|
||||
let result = test();
|
||||
if (result && "promise" in result) {
|
||||
result = result.promise;
|
||||
}
|
||||
if (!result || !("then" in result)) {
|
||||
let exn;
|
||||
try {
|
||||
do_throw("Test " + test + " did not return a promise: " + result);
|
||||
} catch (x) {
|
||||
exn = x;
|
||||
}
|
||||
return Promise.reject(exn);
|
||||
}
|
||||
// The test returns a promise
|
||||
result = result.then(
|
||||
// Test complete
|
||||
function onResolve() {
|
||||
do_print("Test complete: " + test);
|
||||
},
|
||||
// The test failed with an unexpected error
|
||||
function onReject(err) {
|
||||
let detail;
|
||||
if (err && typeof err == "object" && "stack" in err) {
|
||||
detail = err.stack;
|
||||
} else {
|
||||
detail = "(no stack)";
|
||||
}
|
||||
do_throw("Test " + test + " rejected with the following reason: "
|
||||
+ err + detail);
|
||||
});
|
||||
return result;
|
||||
} catch (x) {
|
||||
// The test failed because of an error outside of a promise
|
||||
do_throw("Error in body of test " + test + ": " + x + " at " + x.stack);
|
||||
return Promise.reject();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
396
toolkit/addon-sdk/test/unit/test_promise.js
Normal file
396
toolkit/addon-sdk/test/unit/test_promise.js
Normal file
@ -0,0 +1,396 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
let tests = [];
|
||||
|
||||
// Utility function to observe an failures in a promise
|
||||
// This function is useful if the promise itself is
|
||||
// not returned.
|
||||
let observe_failures = function observe_failures(promise) {
|
||||
promise.then(null, function onReject(reason) {
|
||||
test.do_throw("Observed failure in test " + test + ": " + reason);
|
||||
});
|
||||
};
|
||||
|
||||
// Test that all observers are notified
|
||||
tests.push(make_promise_test(
|
||||
function notification(test) {
|
||||
// The size of the test
|
||||
const SIZE = 10;
|
||||
const RESULT = "this is an arbitrary value";
|
||||
|
||||
// Number of observers that yet need to be notified
|
||||
let expected = SIZE;
|
||||
|
||||
// |true| once an observer has been notified
|
||||
let notified = [];
|
||||
|
||||
// The promise observed
|
||||
let source = Promise.defer();
|
||||
let result = Promise.defer();
|
||||
|
||||
let install_observer = function install_observer(i) {
|
||||
observe_failures(source.promise.then(
|
||||
function onSuccess(value) {
|
||||
do_check_true(!notified[i], "Ensuring that observer is notified at most once");
|
||||
notified[i] = true;
|
||||
|
||||
do_check_eq(value, RESULT, "Ensuring that the observed value is correct");
|
||||
if (--expected == 0) {
|
||||
result.resolve();
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
// Install a number of observers before resolving
|
||||
let i;
|
||||
for (i = 0; i < SIZE/2; ++i) {
|
||||
install_observer(i);
|
||||
}
|
||||
|
||||
source.resolve(RESULT);
|
||||
|
||||
// Install remaining observers
|
||||
for(;i < SIZE; ++i) {
|
||||
install_observer(i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}));
|
||||
|
||||
// Test that all observers are notified at most once, even if source
|
||||
// is resolved/rejected several times
|
||||
tests.push(make_promise_test(
|
||||
function notification_once(test) {
|
||||
// The size of the test
|
||||
const SIZE = 10;
|
||||
const RESULT = "this is an arbitrary value";
|
||||
|
||||
// Number of observers that yet need to be notified
|
||||
let expected = SIZE;
|
||||
|
||||
// |true| once an observer has been notified
|
||||
let notified = [];
|
||||
|
||||
// The promise observed
|
||||
let observed = Promise.defer();
|
||||
let result = Promise.defer();
|
||||
|
||||
let install_observer = function install_observer(i) {
|
||||
observe_failures(observed.promise.then(
|
||||
function onSuccess(value) {
|
||||
do_check_true(!notified[i], "Ensuring that observer is notified at most once");
|
||||
notified[i] = true;
|
||||
|
||||
do_check_eq(value, RESULT, "Ensuring that the observed value is correct");
|
||||
if (--expected == 0) {
|
||||
result.resolve();
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
// Install a number of observers before resolving
|
||||
let i;
|
||||
for (i = 0; i < SIZE/2; ++i) {
|
||||
install_observer(i);
|
||||
}
|
||||
|
||||
observed.resolve(RESULT);
|
||||
|
||||
// Install remaining observers
|
||||
for(;i < SIZE; ++i) {
|
||||
install_observer(i);
|
||||
}
|
||||
|
||||
// Resolve some more
|
||||
for (i = 0; i < 10; ++i) {
|
||||
observed.resolve(RESULT);
|
||||
observed.reject();
|
||||
}
|
||||
|
||||
return result;
|
||||
}));
|
||||
|
||||
// Test that throwing an exception from a onResolve listener
|
||||
// does not prevent other observers from receiving the notification
|
||||
// of success.
|
||||
tests.push(
|
||||
make_promise_test(function exceptions_do_not_stop_notifications(test) {
|
||||
let source = Promise.defer();
|
||||
|
||||
let exception_thrown = false;
|
||||
let exception_content = new Error("Boom!");
|
||||
|
||||
let observer_1 = source.promise.then(
|
||||
function onResolve() {
|
||||
exception_thrown = true;
|
||||
throw exception_content;
|
||||
});
|
||||
|
||||
let observer_2 = source.promise.then(
|
||||
function onResolve() {
|
||||
do_check_true(exception_thrown, "Second observer called after first observer has thrown");
|
||||
}
|
||||
);
|
||||
|
||||
let result = observer_1.then(
|
||||
function onResolve() {
|
||||
do_throw("observer_1 should not have resolved");
|
||||
},
|
||||
function onReject(reason) {
|
||||
do_check_true(reason == exception_content, "Obtained correct rejection");
|
||||
}
|
||||
);
|
||||
|
||||
source.resolve();
|
||||
return result;
|
||||
}
|
||||
));
|
||||
|
||||
// Test that, once a promise is resolved, further resolve/reject
|
||||
// are ignored.
|
||||
tests.push(
|
||||
make_promise_test(function subsequent_resolves_are_ignored(test) {
|
||||
let deferred = Promise.defer();
|
||||
deferred.resolve(1);
|
||||
deferred.resolve(2);
|
||||
deferred.reject(3);
|
||||
|
||||
let result = deferred.promise.then(
|
||||
function onResolve(value) {
|
||||
do_check_eq(value, 1, "Resolution chose the first value");
|
||||
},
|
||||
function onReject(reason) {
|
||||
do_throw("Obtained a rejection while the promise was already resolved");
|
||||
}
|
||||
);
|
||||
|
||||
return result;
|
||||
}));
|
||||
|
||||
// Test that, once a promise is rejected, further resolve/reject
|
||||
// are ignored.
|
||||
tests.push(
|
||||
make_promise_test(function subsequent_rejects_are_ignored(test) {
|
||||
let deferred = Promise.defer();
|
||||
deferred.reject(1);
|
||||
deferred.reject(2);
|
||||
deferred.resolve(3);
|
||||
|
||||
let result = deferred.promise.then(
|
||||
function onResolve() {
|
||||
do_throw("Obtained a resolution while the promise was already rejected");
|
||||
},
|
||||
function onReject(reason) {
|
||||
do_check_eq(reason, 1, "Rejection chose the first value");
|
||||
}
|
||||
);
|
||||
|
||||
return result;
|
||||
}));
|
||||
|
||||
// Test that returning normally from a rejection recovers from the error
|
||||
// and that listeners are informed of a success.
|
||||
tests.push(
|
||||
make_promise_test(function recovery(test) {
|
||||
let boom = new Error("Boom!");
|
||||
let deferred = Promise.defer();
|
||||
const RESULT = "An arbitrary value";
|
||||
|
||||
let promise = deferred.promise.then(
|
||||
function onResolve() {
|
||||
do_throw("A rejected promise should not resolve");
|
||||
},
|
||||
function onReject(reason) {
|
||||
do_check_true(reason == boom, "Promise was rejected with the correct error");
|
||||
return RESULT;
|
||||
}
|
||||
);
|
||||
|
||||
promise = promise.then(
|
||||
function onResolve(value) {
|
||||
do_check_eq(value, RESULT, "Promise was recovered with the correct value");
|
||||
}
|
||||
);
|
||||
|
||||
deferred.reject(boom);
|
||||
return promise;
|
||||
}));
|
||||
|
||||
// Test that returning a resolved promise from a onReject causes a resolution
|
||||
// (recovering from the error) and that returning a rejected promise
|
||||
// from a onResolve listener causes a rejection (raising an error).
|
||||
tests.push(
|
||||
make_promise_test(function recovery_with_promise(test) {
|
||||
let boom = new Error("Arbitrary error");
|
||||
let deferred = Promise.defer();
|
||||
const RESULT = "An arbitrary value";
|
||||
const boom2 = new Error("Another arbitrary error");
|
||||
|
||||
// return a resolved promise from a onReject listener
|
||||
let promise = deferred.promise.then(
|
||||
function onResolve() {
|
||||
do_throw("A rejected promise should not resolve");
|
||||
},
|
||||
function onReject(reason) {
|
||||
do_check_true(reason == boom, "Promise was rejected with the correct error");
|
||||
return Promise.resolve(RESULT);
|
||||
}
|
||||
);
|
||||
|
||||
// return a rejected promise from a onResolve listener
|
||||
promise = promise.then(
|
||||
function onResolve(value) {
|
||||
do_check_eq(value, RESULT, "Promise was recovered with the correct value");
|
||||
return Promise.reject(boom2);
|
||||
}
|
||||
);
|
||||
|
||||
promise = promise.then(
|
||||
null,
|
||||
function onReject(reason) {
|
||||
do_check_eq(reason, boom2, "Rejection was propagated with the correct " +
|
||||
"reason, through a promise");
|
||||
}
|
||||
);
|
||||
|
||||
deferred.reject(boom);
|
||||
return promise;
|
||||
}));
|
||||
|
||||
// Test that we can resolve with promises of promises
|
||||
tests.push(
|
||||
make_promise_test(function test_propagation(test) {
|
||||
const RESULT = "Yet another arbitrary value";
|
||||
let d1 = Promise.defer();
|
||||
let d2 = Promise.defer();
|
||||
let d3 = Promise.defer();
|
||||
|
||||
d3.resolve(d2.promise);
|
||||
d2.resolve(d1.promise);
|
||||
d1.resolve(RESULT);
|
||||
|
||||
return d3.promise.then(
|
||||
function onSuccess(value) {
|
||||
do_check_eq(value, RESULT, "Resolution with a promise eventually yielded "
|
||||
+ " the correct result");
|
||||
}
|
||||
);
|
||||
}));
|
||||
|
||||
// Test sequences of |then|
|
||||
tests.push(
|
||||
make_promise_test(function test_chaining(test) {
|
||||
let error_1 = new Error("Error 1");
|
||||
let error_2 = new Error("Error 2");
|
||||
let result_1 = "First result";
|
||||
let result_2 = "Second result";
|
||||
let result_3 = "Third result";
|
||||
|
||||
let source = Promise.defer();
|
||||
|
||||
let promise = source.promise.then().then();
|
||||
|
||||
source.resolve(result_1);
|
||||
|
||||
// Check that result_1 is correctly propagated
|
||||
promise = promise.then(
|
||||
function onSuccess(result) {
|
||||
do_check_eq(result, result_1, "Result was propagated correctly through " +
|
||||
" several applications of |then|");
|
||||
return result_2;
|
||||
}
|
||||
);
|
||||
|
||||
// Check that returning from the promise produces a resolution
|
||||
promise = promise.then(
|
||||
null,
|
||||
function onReject() {
|
||||
do_throw("Incorrect rejection");
|
||||
}
|
||||
);
|
||||
|
||||
// ... and that the check did not alter the value
|
||||
promise = promise.then(
|
||||
function onResolve(value) {
|
||||
do_check_eq(value, result_2, "Result was propagated correctly once again");
|
||||
}
|
||||
);
|
||||
|
||||
// Now the same kind of tests for rejections
|
||||
promise = promise.then(
|
||||
function onResolve() {
|
||||
throw error_1;
|
||||
}
|
||||
);
|
||||
|
||||
promise = promise.then(
|
||||
function onResolve() {
|
||||
do_throw("Incorrect resolution: the exception should have caused a rejection");
|
||||
}
|
||||
);
|
||||
|
||||
promise = promise.then(
|
||||
null,
|
||||
function onReject(reason) {
|
||||
do_check_true(reason == error_1, "Reason was propagated correctly");
|
||||
throw error_2;
|
||||
}
|
||||
);
|
||||
|
||||
promise = promise.then(
|
||||
null,
|
||||
function onReject(reason) {
|
||||
do_check_true(reason == error_2, "Throwing an error altered the reason " +
|
||||
"as expected");
|
||||
return result_3;
|
||||
}
|
||||
);
|
||||
|
||||
promise = promise.then(
|
||||
function onResolve(result) {
|
||||
do_check_eq(result, result_3, "Error was correctly recovered");
|
||||
}
|
||||
);
|
||||
|
||||
return promise;
|
||||
}));
|
||||
|
||||
// Test that resolving with a rejected promise actually rejects
|
||||
tests.push(
|
||||
make_promise_test(function resolve_to_rejected(test) {
|
||||
let source = Promise.defer();
|
||||
let error = new Error("Boom");
|
||||
|
||||
let promise = source.promise.then(
|
||||
function onResolve() {
|
||||
do_throw("Incorrect call to onResolve listener");
|
||||
},
|
||||
function onReject(reason) {
|
||||
do_check_eq(reason, error, "Rejection lead to the expected reason");
|
||||
}
|
||||
);
|
||||
|
||||
source.resolve(Promise.reject(error));
|
||||
|
||||
return promise;
|
||||
}));
|
||||
|
||||
// Test that Promise.resolve resolves as expected
|
||||
tests.push(
|
||||
make_promise_test(function test_resolve(test) {
|
||||
const RESULT = "arbitrary value";
|
||||
let promise = Promise.resolve(RESULT).then(
|
||||
function onResolve(result) {
|
||||
do_check_eq(result, RESULT, "Promise.resolve propagated the correct result");
|
||||
}
|
||||
);
|
||||
return promise;
|
||||
}));
|
||||
|
||||
function run_test()
|
||||
{
|
||||
do_test_pending();
|
||||
run_promise_tests(tests, do_test_finished);
|
||||
}
|
5
toolkit/addon-sdk/test/unit/xpcshell.ini
Normal file
5
toolkit/addon-sdk/test/unit/xpcshell.ini
Normal file
@ -0,0 +1,5 @@
|
||||
[DEFAULT]
|
||||
head = head.js
|
||||
tail =
|
||||
|
||||
[test_promise.js]
|
Loading…
x
Reference in New Issue
Block a user