diff options
Diffstat (limited to 'js/src/tests/non262/Promise')
25 files changed, 1032 insertions, 0 deletions
diff --git a/js/src/tests/non262/Promise/allSettled.js b/js/src/tests/non262/Promise/allSettled.js new file mode 100644 index 0000000000..7644ea97a8 --- /dev/null +++ b/js/src/tests/non262/Promise/allSettled.js @@ -0,0 +1,86 @@ +// Smoke test for `Promise.allSettled`, test262 should cover the function in +// more detail. + +// Empty elements. +Promise.allSettled([]).then(v => { + assertDeepEq(v, []); +}); + +// Single element. +Promise.allSettled([Promise.resolve(0)]).then(v => { + assertDeepEq(v, [ + {"status": "fulfilled", "value": 0}, + ]); +}); +Promise.allSettled([Promise.reject(1)]).then(v => { + assertDeepEq(v, [ + {"status": "rejected", "reason": 1}, + ]); +}); + +// Multiple elements. +Promise.allSettled([Promise.resolve(1), Promise.resolve(2)]).then(v => { + assertDeepEq(v, [ + {"status": "fulfilled", "value": 1}, + {"status": "fulfilled", "value": 2}, + ]); +}); +Promise.allSettled([Promise.resolve(3), Promise.reject(4)]).then(v => { + assertDeepEq(v, [ + {"status": "fulfilled", "value": 3}, + {"status": "rejected", "reason": 4}, + ]); +}); +Promise.allSettled([Promise.reject(5), Promise.resolve(6)]).then(v => { + assertDeepEq(v, [ + {"status": "rejected", "reason": 5}, + {"status": "fulfilled", "value": 6}, + ]); +}); +Promise.allSettled([Promise.reject(7), Promise.reject(8)]).then(v => { + assertDeepEq(v, [ + {"status": "rejected", "reason": 7}, + {"status": "rejected", "reason": 8}, + ]); +}); + +// Cross-Realm tests. +// +// Note: When |g| is a cross-compartment global, Promise.allSettled creates +// the result array in |g|'s Realm. This doesn't follow the spec, but the code +// in js/src/builtin/Promise.cpp claims this is useful when the Promise +// compartment is less-privileged. This means for this test we can't use +// assertDeepEq below, because the result array may have the wrong prototype. +let g = newGlobal(); + +if (typeof isSameCompartment !== "function") { + var isSameCompartment = SpecialPowers.Cu.getJSTestingFunctions().isSameCompartment; +} + +// Test wrapping when neither Promise.allSettled element function is called. +Promise.allSettled.call(g.Promise, []).then(v => { + assertEq(isSameCompartment(v, g), true); + + assertEq(v.length, 0); +}); + +// Test wrapping in `Promise.allSettled Resolve Element Function`. +Promise.allSettled.call(g.Promise, [Promise.resolve(0)]).then(v => { + assertEq(isSameCompartment(v, g), true); + + assertEq(v.length, 1); + assertEq(v[0].status, "fulfilled"); + assertEq(v[0].value, 0); +}); + +// Test wrapping in `Promise.allSettled Reject Element Function`. +Promise.allSettled.call(g.Promise, [Promise.reject(0)]).then(v => { + assertEq(isSameCompartment(v, g), true); + + assertEq(v.length, 1); + assertEq(v[0].status, "rejected"); + assertEq(v[0].reason, 0); +}); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/non262/Promise/any-stack-overflow.js b/js/src/tests/non262/Promise/any-stack-overflow.js new file mode 100644 index 0000000000..8f3919e90a --- /dev/null +++ b/js/src/tests/non262/Promise/any-stack-overflow.js @@ -0,0 +1,14 @@ +// Bug 1646317 - Don't assert on stack overflow under Promise.any(). + +if ("ignoreUnhandledRejections" in this) { + ignoreUnhandledRejections(); +} + +Array.prototype[Symbol.iterator] = function*() { + let rejected = Promise.reject(0); + let p = Promise.any([rejected]); +} +new Set(Object.keys(this)); +new Set(Object.keys(this)); + +this.reportCompare && reportCompare(true, true); diff --git a/js/src/tests/non262/Promise/any-stack.js b/js/src/tests/non262/Promise/any-stack.js new file mode 100644 index 0000000000..dacb9e7f73 --- /dev/null +++ b/js/src/tests/non262/Promise/any-stack.js @@ -0,0 +1,69 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs async stack capture + +function toMessage(stack) { + // Provide the stack string in the error message for debugging. + return `[stack: ${stack.replace(/\n/g, "\\n")}]`; +} + +// Test when AggregateError isn't created from a Promise Job. +{ + let p = Promise.any([]); // line 10 + + p.then(v => { + reportCompare(0, 1, "expected error"); + }, e => { + assertEq(e.name, "AggregateError"); + var {stack} = e; + + assertEq(/^@.+any-stack.js:10/m.test(stack), true, toMessage(stack)); + }); +} + +// Same as above, but now with surrounding function context. +function testNoJobQueue() { + let p = Promise.any([]); // line 24 + + p.then(v => { + reportCompare(0, 1, "expected error"); + }, e => { + assertEq(e.name, "AggregateError"); + var {stack} = e; + + assertEq(/^testNoJobQueue@.+any-stack.js:24/m.test(stack), true, toMessage(stack)); + }); +} +testNoJobQueue(); + +// Test when AggregateError is created from a Promise Job. +{ + let rejected = Promise.reject(0); + let p = Promise.any([rejected]); // line 40 + + p.then(v => { + reportCompare(0, 1, "expected error"); + }, e => { + assertEq(e.name, "AggregateError"); + var {stack} = e; + + assertEq(/^Promise.any\*@.+any-stack.js:40/m.test(stack), true, toMessage(stack)); + }); +} + +// Same as above, but now with surrounding function context. +function testFromJobQueue() { + let rejected = Promise.reject(0); + let p = Promise.any([rejected]); // line 55 + + p.then(v => { + reportCompare(0, 1, "expected error"); + }, e => { + assertEq(e.name, "AggregateError"); + var {stack} = e; + + assertEq(/^Promise.any\*testFromJobQueue@.+any-stack.js:55/m.test(stack), true, toMessage(stack)); + }); +} +testFromJobQueue(); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/non262/Promise/any.js b/js/src/tests/non262/Promise/any.js new file mode 100644 index 0000000000..ff5e676bf5 --- /dev/null +++ b/js/src/tests/non262/Promise/any.js @@ -0,0 +1,76 @@ +// Smoke test for `Promise.any`, test262 should cover the function in +// more detail. + +function expectedError() { + reportCompare(true, false, "expected error"); +} + +// Empty elements. +Promise.any([]).then(expectedError, e => { + assertEq(e instanceof AggregateError, true); + assertEq(e.errors.length, 0); +}); + +// Single element. +Promise.any([Promise.resolve(0)]).then(v => { + assertEq(v, 0); +}); +Promise.any([Promise.reject(1)]).then(expectedError, e => { + assertEq(e instanceof AggregateError, true); + assertEq(e.errors.length, 1); + assertEq(e.errors[0], 1); +}); + +// Multiple elements. +Promise.any([Promise.resolve(1), Promise.resolve(2)]).then(v => { + assertEq(v, 1); +}); +Promise.any([Promise.resolve(3), Promise.reject(4)]).then(v => { + assertEq(v, 3); +}); +Promise.any([Promise.reject(5), Promise.resolve(6)]).then(v => { + assertEq(v, 6); +}); +Promise.any([Promise.reject(7), Promise.reject(8)]).then(expectedError, e => { + assertEq(e instanceof AggregateError, true); + assertEq(e.errors.length, 2); + assertEq(e.errors[0], 7); + assertEq(e.errors[1], 8); +}); + +// Cross-Realm tests. +// +// Note: When |g| is a cross-compartment global, Promise.any creates the errors +// array and the AggregateError in |g|'s Realm. This doesn't follow the spec, but +// the code in js/src/builtin/Promise.cpp claims this is useful when the Promise +// compartment is less-privileged. This means for this test we can't use +// assertDeepEq below, because the result array/error may have the wrong prototype. +let g = newGlobal(); + +if (typeof isSameCompartment !== "function") { + var isSameCompartment = SpecialPowers.Cu.getJSTestingFunctions().isSameCompartment; +} + +// Test wrapping when no `Promise.any Reject Element Function` is called. +Promise.any.call(g.Promise, []).then(expectedError, e => { + assertEq(e.name, "AggregateError"); + + assertEq(isSameCompartment(e, g), true); + assertEq(isSameCompartment(e.errors, g), true); + + assertEq(e.errors.length, 0); +}); + +// Test wrapping in `Promise.any Reject Element Function`. +Promise.any.call(g.Promise, [Promise.reject("err")]).then(expectedError, e => { + assertEq(e.name, "AggregateError"); + + assertEq(isSameCompartment(e, g), true); + assertEq(isSameCompartment(e.errors, g), true); + + assertEq(e.errors.length, 1); + assertEq(e.errors[0], "err"); +}); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/non262/Promise/browser.js b/js/src/tests/non262/Promise/browser.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/js/src/tests/non262/Promise/browser.js diff --git a/js/src/tests/non262/Promise/bug-1287334.js b/js/src/tests/non262/Promise/bug-1287334.js new file mode 100644 index 0000000000..19b9ca0aa0 --- /dev/null +++ b/js/src/tests/non262/Promise/bug-1287334.js @@ -0,0 +1,7 @@ +var promise = Promise.resolve(1); +var FakeCtor = function(exec){ exec(function(){}, function(){}); }; +Object.defineProperty(Promise, Symbol.species, {value: FakeCtor}); +// This just shouldn't crash. It does without bug 1287334 fixed. +promise.then(function(){}); + +this.reportCompare && reportCompare(true, true); diff --git a/js/src/tests/non262/Promise/bug-1288382.js b/js/src/tests/non262/Promise/bug-1288382.js new file mode 100644 index 0000000000..0a673a9caa --- /dev/null +++ b/js/src/tests/non262/Promise/bug-1288382.js @@ -0,0 +1,8 @@ +// This just shouldn't trigger a failed assert. +// It does without bug 1288382 fixed. +Promise.all.call(class { + constructor(exec){ exec(()=>{}, ()=>{}); } + static resolve() { return {then(){}}; } +}, [null]); + +this.reportCompare && reportCompare(true, true); diff --git a/js/src/tests/non262/Promise/bug-1289040.js b/js/src/tests/non262/Promise/bug-1289040.js new file mode 100644 index 0000000000..f5a9fd98e4 --- /dev/null +++ b/js/src/tests/non262/Promise/bug-1289040.js @@ -0,0 +1,8 @@ +var global = newGlobal(); +Promise.prototype.then = global.Promise.prototype.then; +p1 = new Promise(function f(r) { + r(1); +}); +p2 = p1.then(function g(){}); + +this.reportCompare && reportCompare(true,true); diff --git a/js/src/tests/non262/Promise/bug-1792196.js b/js/src/tests/non262/Promise/bug-1792196.js new file mode 100644 index 0000000000..946f5737de --- /dev/null +++ b/js/src/tests/non262/Promise/bug-1792196.js @@ -0,0 +1,18 @@ +// |reftest| skip-if(!this.hasOwnProperty('settlePromiseNow')) + +function main() { + function v0(v1) { + throw "foobar"; + } + const v8 = new Promise(v0); + const v9 = v8.catch(); + const v11 = this.settlePromiseNow(v9); + function v12(v13) { + } + const v15 = new Promise(v11); + const v16 = v15.catch(v12); + gc(); +} +main(); + +this.reportCompare && reportCompare(true, true); diff --git a/js/src/tests/non262/Promise/dependent-promises.js b/js/src/tests/non262/Promise/dependent-promises.js new file mode 100644 index 0000000000..d0e2bfa8e8 --- /dev/null +++ b/js/src/tests/non262/Promise/dependent-promises.js @@ -0,0 +1,36 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs Debugger + +var g = newGlobal({newCompartment: true}); +var dbg = new Debugger(g); +var gw = dbg.addDebuggee(g); + +g.eval(` +var p = new Promise(() => {}); +p.name = "p"; +var q = p.then(); +q.name = "q"; +var r = p.then(null, () => {}); +r.name = "r"; +var s = Promise.all([p, q]); +s.name = "s"; +var t = Promise.race([r, s]); +t.name = "t"; +`); + +function getDependentNames(promise) { + return gw.makeDebuggeeValue(promise).promiseDependentPromises.map((p) => p.getOwnPropertyDescriptor('name').value); +} + +function arraysEqual(arr1, arr2, msg) { + assertEq(arr1.length, arr2.length, msg + ": length"); + for (var i = 0; i < arr1.length; ++i) { + assertEq(arr1[i], arr2[i], msg + ": [" + i + "]"); + } +} + +arraysEqual(getDependentNames(g.p), ["q", "r", "s"], "deps for p"); +arraysEqual(getDependentNames(g.q), ["s"], "deps for q"); +arraysEqual(getDependentNames(g.r), ["t"], "deps for r"); +arraysEqual(getDependentNames(g.s), ["t"], "deps for s"); + +this.reportCompare && reportCompare(true,true); diff --git a/js/src/tests/non262/Promise/enqueue-promise-reactions.js b/js/src/tests/non262/Promise/enqueue-promise-reactions.js new file mode 100644 index 0000000000..e7a6c2426e --- /dev/null +++ b/js/src/tests/non262/Promise/enqueue-promise-reactions.js @@ -0,0 +1,38 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs getSelfHostedValue and drainJobQueue + +function onResolved(val) { + result = 'resolved with ' + val; +} + +function onRejected(val) { + result = 'rejected with ' + val; +} + +// Replacing `Promise#then` shouldn't affect addPromiseReactions. +Promise.prototype.then = 1; + +// Replacing Promise@@species shouldn't affect addPromiseReactions. +Object.defineProperty(Promise, Symbol.species, { get: function(){} }); + +// Replacing `Promise` shouldn't affect addPromiseReactions. +let PromiseCtor = Promise; +Promise = {}; + +let result; +let res; +let rej; +let p = new PromiseCtor(function(res_, rej_) { res = res_; rej = rej_; }); + +addPromiseReactions(p, onResolved, onRejected); +res('foo'); +drainJobQueue(); +assertEq(result, 'resolved with foo') + +p = new PromiseCtor(function(res_, rej_) { res = res_; rej = rej_; }); + +addPromiseReactions(p, onResolved, onRejected); +rej('bar'); +drainJobQueue(); +assertEq(result, 'rejected with bar'); + +this.reportCompare && reportCompare(true,true); diff --git a/js/src/tests/non262/Promise/for-of-iterator-uses-getv.js b/js/src/tests/non262/Promise/for-of-iterator-uses-getv.js new file mode 100644 index 0000000000..28c888d7a3 --- /dev/null +++ b/js/src/tests/non262/Promise/for-of-iterator-uses-getv.js @@ -0,0 +1,25 @@ +"use strict"; // Use strict-mode to ensure |this| arguments aren't converted to objects. + +var emptyIterator = { + next() { + return {done: true}; + } +}; + +Object.defineProperty(Number.prototype, Symbol.iterator, { + configurable: true, + get() { + assertEq(typeof this, "number"); + return function() { + assertEq(typeof this, "number"); + return emptyIterator; + } + } +}); + +Promise.all(0); +Promise.allSettled(0); +Promise.race(0); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/non262/Promise/get-wait-for-all-promise.js b/js/src/tests/non262/Promise/get-wait-for-all-promise.js new file mode 100644 index 0000000000..69464ce08b --- /dev/null +++ b/js/src/tests/non262/Promise/get-wait-for-all-promise.js @@ -0,0 +1,60 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs getSelfHostedValue and drainJobQueue + +function onResolved(val) { + result = 'resolved with ' + val; +} + +function onRejected(val) { + result = 'rejected with ' + val; +} + +// Replacing `Promise#then` shouldn't affect getWaitForAllPromise. +let originalThen = Promise.prototype.then; +Promise.prototype.then = 1; + +// Replacing Promise[@@species] shouldn't affect getWaitForAllPromise. +Object.defineProperty(Promise, Symbol.species, { get: function(){} }); + +// Replacing `Promise` shouldn't affect getWaitForAllPromise. +let PromiseCtor = Promise; +Promise = {}; + +// Replacing Array[@@iterator] shouldn't affect getWaitForAllPromise. +Array.prototype[Symbol.iterator] = function(){}; + +let resolveFunctions = []; +let rejectFunctions = []; +let promises = []; +for (let i = 0; i < 3; i++) { + let p = new PromiseCtor(function(res_, rej_) { + resolveFunctions.push(res_); + rejectFunctions.push(rej_); + }); + promises.push(p); +} + +let allPromise = getWaitForAllPromise(promises); +let then = originalThen.call(allPromise, onResolved, onRejected); + +resolveFunctions.forEach((fun, i)=>fun(i)); +drainJobQueue(); + +assertEq(result, 'resolved with 0,1,2'); + +// Empty lists result in a promise resolved with an empty array. +result = undefined; +originalThen.call(getWaitForAllPromise([]), v=>(result = v)); +drainJobQueue(); +assertEq(result instanceof Array, true); +assertEq(result.length, 0); + +//Empty lists result in a promise resolved with an empty array. +result = undefined; +originalThen.call(getWaitForAllPromise([]), v=>(result = v)); + +drainJobQueue(); + +assertEq(result instanceof Array, true); +assertEq(result.length, 0); + +this.reportCompare && reportCompare(true,true); diff --git a/js/src/tests/non262/Promise/iterator-close.js b/js/src/tests/non262/Promise/iterator-close.js new file mode 100644 index 0000000000..b3127e87f2 --- /dev/null +++ b/js/src/tests/non262/Promise/iterator-close.js @@ -0,0 +1,234 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue + +var BUGNUMBER = 1180306; +var summary = 'Promise.{all,race} should close iterator on error'; + +print(BUGNUMBER + ": " + summary); + +function test(ctor, props, { nextVal=undefined, + nextThrowVal=undefined, + modifier=undefined, + rejectReason=undefined, + rejectType=undefined, + closed=true }) { + function getIterable() { + let iterable = { + closed: false, + [Symbol.iterator]() { + let iterator = { + first: true, + next() { + if (this.first) { + this.first = false; + if (nextThrowVal) + throw nextThrowVal; + return nextVal; + } + return { value: undefined, done: true }; + }, + return() { + iterable.closed = true; + return {}; + } + }; + if (modifier) + modifier(iterator, iterable); + + return iterator; + } + }; + return iterable; + } + for (let prop of props) { + let iterable = getIterable(); + let e; + ctor[prop](iterable).catch(e_ => { e = e_; }); + drainJobQueue(); + if(rejectType) + assertEq(e instanceof rejectType, true); + else + assertEq(e, rejectReason); + assertEq(iterable.closed, closed); + } +} + +// == Error cases with close == + +// ES 2017 draft 25.4.4.1.1 step 6.i. +// ES 2017 draft 25.4.4.3.1 step 3.h. +class MyPromiseStaticResolveGetterThrows extends Promise { + static get resolve() { + throw "static resolve getter throws"; + } +}; +test(MyPromiseStaticResolveGetterThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + rejectReason: "static resolve getter throws", + closed: false, +}); + +class MyPromiseStaticResolveThrows extends Promise { + static resolve() { + throw "static resolve throws"; + } +}; +test(MyPromiseStaticResolveThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + rejectReason: "static resolve throws", + closed: true, +}); + +// ES 2017 draft 25.4.4.1.1 step 6.q. +// ES 2017 draft 25.4.4.3.1 step 3.i. +class MyPromiseThenGetterThrows extends Promise { + static resolve() { + return { + get then() { + throw "then getter throws"; + } + }; + } +}; +test(MyPromiseThenGetterThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + rejectReason: "then getter throws", + closed: true, +}); + +class MyPromiseThenThrows extends Promise { + static resolve() { + return { + then() { + throw "then throws"; + } + }; + } +}; +test(MyPromiseThenThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + rejectReason: "then throws", + closed: true, +}); + +// ES 2017 draft 7.4.6 step 3. +// if GetMethod fails, the thrown value should be used. +test(MyPromiseThenThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + modifier: (iterator, iterable) => { + Object.defineProperty(iterator, "return", { + get: function() { + iterable.closed = true; + throw "return getter throws"; + } + }); + }, + rejectReason: "return getter throws", + closed: true, +}); +test(MyPromiseThenThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + modifier: (iterator, iterable) => { + Object.defineProperty(iterator, "return", { + get: function() { + iterable.closed = true; + return "non object"; + } + }); + }, + rejectType: TypeError, + closed: true, +}); +test(MyPromiseThenThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + modifier: (iterator, iterable) => { + Object.defineProperty(iterator, "return", { + get: function() { + iterable.closed = true; + // Non callable. + return {}; + } + }); + }, + rejectType: TypeError, + closed: true, +}); + +// ES 2017 draft 7.4.6 steps 6. +// if return method throws, the thrown value should be ignored. +test(MyPromiseThenThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + modifier: (iterator, iterable) => { + iterator.return = function() { + iterable.closed = true; + throw "return throws"; + }; + }, + rejectReason: "then throws", + closed: true, +}); + +test(MyPromiseThenThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + modifier: (iterator, iterable) => { + iterator.return = function() { + iterable.closed = true; + return "non object"; + }; + }, + rejectReason: "then throws", + closed: true, +}); + +// == Error cases without close == + +// ES 2017 draft 25.4.4.1.1 step 6.a. +test(Promise, ["all", "race"], { + nextThrowVal: "next throws", + rejectReason: "next throws", + closed: false, +}); + +test(Promise, ["all", "race"], { + nextVal: { value: {}, get done() { throw "done getter throws"; } }, + rejectReason: "done getter throws", + closed: false, +}); + +// ES 2017 draft 25.4.4.1.1 step 6.e. +test(Promise, ["all", "race"], { + nextVal: { get value() { throw "value getter throws"; }, done: false }, + rejectReason: "value getter throws", + closed: false, +}); + +// ES 2017 draft 25.4.4.1.1 step 6.d.iii.2. +let first = true; +class MyPromiseResolveThrows extends Promise { + constructor(executer) { + if (first) { + first = false; + super((resolve, reject) => { + executer(() => { + throw "resolve throws"; + }, reject); + }); + return; + } + super(executer); + } +}; +test(MyPromiseResolveThrows, ["all"], { + nextVal: { value: undefined, done: true }, + rejectReason: "resolve throws", + closed: false, +}); + +// == Successful cases == + +test(Promise, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + closed: false, +}); + +if (typeof reportCompare === 'function') + reportCompare(true, true); diff --git a/js/src/tests/non262/Promise/iterator-primitive.js b/js/src/tests/non262/Promise/iterator-primitive.js new file mode 100644 index 0000000000..cfc3a4ed32 --- /dev/null +++ b/js/src/tests/non262/Promise/iterator-primitive.js @@ -0,0 +1,28 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue + +var BUGNUMBER = 1021835; +var summary = "Returning non-object from @@iterator should throw"; + +print(BUGNUMBER + ": " + summary); + +let primitives = [ + 1, + true, + undefined, + null, + "foo", + Symbol.iterator +]; + +for (let primitive of primitives) { + let arg = { + [Symbol.iterator]() { + return primitive; + } + }; + assertEventuallyThrows(Promise.all(arg), TypeError); + assertEventuallyThrows(Promise.race(arg), TypeError); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/non262/Promise/methods-non-enumerable.js b/js/src/tests/non262/Promise/methods-non-enumerable.js new file mode 100644 index 0000000000..ea2228894b --- /dev/null +++ b/js/src/tests/non262/Promise/methods-non-enumerable.js @@ -0,0 +1,4 @@ +assertEq(Object.keys(Promise).length, 0); +assertEq(Object.keys(Promise.prototype).length, 0); + +reportCompare(0, 0, "ok"); diff --git a/js/src/tests/non262/Promise/promise-all.js b/js/src/tests/non262/Promise/promise-all.js new file mode 100644 index 0000000000..3ae58fc384 --- /dev/null +++ b/js/src/tests/non262/Promise/promise-all.js @@ -0,0 +1,25 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue + +let results = []; + +let p1 = new Promise(res=>res('result')) + .then(val=>{results.push('then ' + val); return 'first then rval';}) + .then(val=>{results.push('chained then with val: ' + val); return 'p1 then, then'}); + +let p2 = new Promise((res, rej)=>rej('rejection')) + .catch(val=> {results.push('catch ' + val); return results.length;}) + .then(val=>{results.push('then after catch with val: ' + val); return 'p2 catch, then'}, + val=>{throw new Error("mustn't be called")}); + +Promise.all([p1, p2]).then(res => results.push(res + '')); + +drainJobQueue(); + +assertEq(results.length, 5); +assertEq(results[0], 'then result'); +assertEq(results[1], 'catch rejection'); +assertEq(results[2], 'chained then with val: first then rval'); +assertEq(results[3], 'then after catch with val: 2'); +assertEq(results[4], 'p1 then, then,p2 catch, then'); + +this.reportCompare && reportCompare(true,true); diff --git a/js/src/tests/non262/Promise/promise-basics.js b/js/src/tests/non262/Promise/promise-basics.js new file mode 100644 index 0000000000..ad186537fe --- /dev/null +++ b/js/src/tests/non262/Promise/promise-basics.js @@ -0,0 +1,96 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue + +let results = []; + +new Promise(res=>res('result')) + .then(val=>{results.push('then ' + val); return 'first then rval';}) + .then(val=>results.push('chained then with val: ' + val)); + +new Promise((res, rej)=>rej('rejection')) + .catch(val=>{results.push('catch ' + val); return results.length;}) + .then(val=>results.push('then after catch with val: ' + val), + val=>{throw new Error("mustn't be called")}); + +new Promise((res, rej)=> {res('result'); rej('rejection'); }) + .catch(val=>{throw new Error("mustn't be called");}) + .then(val=>results.push('then after resolve+reject with val: ' + val), + val=>{throw new Error("mustn't be called")}); + +new Promise((res, rej)=> { rej('rejection'); res('result'); }) + .catch(val=>{results.push('catch after reject+resolve with val: ' + val);}) + + +drainJobQueue(); + +assertEq(results.length, 6); +assertEq(results[0], 'then result'); +assertEq(results[1], 'catch rejection'); +assertEq(results[2], 'catch after reject+resolve with val: rejection'); +assertEq(results[3], 'chained then with val: first then rval'); +assertEq(results[4], 'then after catch with val: 2'); +assertEq(results[5], 'then after resolve+reject with val: result'); + +results = []; + +Promise.resolve('resolution').then(res=>results.push(res), + rej=>{ throw new Error("mustn't be called"); }); + +let thenCalled = false; +Promise.reject('rejection').then(_=>{thenCalled = true}, + rej=>results.push(rej)); + +drainJobQueue(); + +assertEq(thenCalled, false); +assertEq(results.length, 2); +assertEq(results[0], 'resolution'); +assertEq(results[1], 'rejection'); + + +function callback() {} + +// Calling the executor function with content functions shouldn't assert: +Promise.resolve.call(function(exec) { exec(callback, callback); }); +Promise.reject.call(function(exec) { exec(callback, callback); }); +Promise.all.call(function(exec) { exec(callback, callback); }); +Promise.race.call(function(exec) { exec(callback, callback); }); + +let resolveResult = undefined; +function resolveFun() {resolveResult = "resolveCalled";} +Promise.resolve.call(function(exec) { exec(resolveFun, callback); }); +assertEq(resolveResult, "resolveCalled"); + +let rejectResult = undefined; +function rejectFun() {rejectResult = "rejectCalled";} +Promise.reject.call(function(exec) { exec(callback, rejectFun); }); +assertEq(rejectResult, "rejectCalled"); + +// These should throw: +var wasCalled = false; +var hasThrown = false; +try { + // Calling the executor function twice, providing a resolve callback both times. + Promise.resolve.call(function(executor) { + wasCalled = true; + executor(callback, undefined); + executor(callback, callback); + }); +} catch (e) { + hasThrown = true; +} +assertEq(wasCalled, true); +assertEq(hasThrown, true); + +var hasThrown = false; +try { + // Calling the executor function twice, providing a reject callback both times. + Promise.resolve.call(function(executor) { + executor(undefined, callback); + executor(callback, callback); + }); +} catch (e) { + hasThrown = true; +} +assertEq(hasThrown, true); + +this.reportCompare && reportCompare(0, 0, "ok"); diff --git a/js/src/tests/non262/Promise/promise-rejection-tracking-optimized.js b/js/src/tests/non262/Promise/promise-rejection-tracking-optimized.js new file mode 100644 index 0000000000..c085b9dba8 --- /dev/null +++ b/js/src/tests/non262/Promise/promise-rejection-tracking-optimized.js @@ -0,0 +1,34 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs setPromiseRejectionTrackerCallback + +const UNHANDLED = 0; +const HANDLED = 1; + +let rejections = new Map(); +function rejectionTracker(promise, state) { + rejections.set(promise, state); +} +setPromiseRejectionTrackerCallback(rejectionTracker); + +// If the return value of then is not used, the promise object is optimized +// away, but if a rejection happens, the rejection should be notified. +Promise.resolve().then(() => { throw 1; }); +drainJobQueue(); + +assertEq(rejections.size, 1); + +let [[promise, state]] = rejections; +assertEq(state, UNHANDLED); + +let exc; +promise.catch(x => { exc = x; }); +drainJobQueue(); + +// we handled it after all +assertEq(rejections.get(promise), HANDLED); + +// the right exception was reported +assertEq(exc, 1); + +if (this.reportCompare) { + reportCompare(true,true); +} diff --git a/js/src/tests/non262/Promise/promise-rejection-tracking.js b/js/src/tests/non262/Promise/promise-rejection-tracking.js new file mode 100644 index 0000000000..a596fa6a99 --- /dev/null +++ b/js/src/tests/non262/Promise/promise-rejection-tracking.js @@ -0,0 +1,31 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs setPromiseRejectionTrackerCallback + +const UNHANDLED = 0; +const HANDLED = 1; + +let rejections = new Map(); +function rejectionTracker(promise, state) { + rejections.set(promise, state); +} +setPromiseRejectionTrackerCallback(rejectionTracker); + +// Unhandled rejections are tracked. +let reject; +let p = new Promise((res_, rej_) => (reject = rej_)); +assertEq(rejections.has(p), false); +reject('reason'); +assertEq(rejections.get(p), UNHANDLED); +// Later handling updates the tracking. +p.then(_=>_, _=>_); +assertEq(rejections.get(p), HANDLED); + +rejections.clear(); + +// Handled rejections aren't tracked at all. +p = new Promise((res_, rej_) => (reject = rej_)); +assertEq(rejections.has(p), false); +p.then(_=>_, _=>_); +reject('reason'); +assertEq(rejections.has(p), false); + +this.reportCompare && reportCompare(true,true); diff --git a/js/src/tests/non262/Promise/promise-species.js b/js/src/tests/non262/Promise/promise-species.js new file mode 100644 index 0000000000..52a4ecb632 --- /dev/null +++ b/js/src/tests/non262/Promise/promise-species.js @@ -0,0 +1,8 @@ +assertEq(Promise[Symbol.species], Promise); +let prop = Object.getOwnPropertyDescriptor(Promise, Symbol.species); +assertEq('get' in prop, true); +assertEq(typeof prop.get, 'function'); +assertEq('set' in prop, true); +assertEq(prop.set, undefined); + +reportCompare(0, 0, "ok"); diff --git a/js/src/tests/non262/Promise/promise-subclassing.js b/js/src/tests/non262/Promise/promise-subclassing.js new file mode 100644 index 0000000000..7232205f45 --- /dev/null +++ b/js/src/tests/non262/Promise/promise-subclassing.js @@ -0,0 +1,62 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue + +let results = []; + +class SubPromise extends Promise { + constructor(executor) { + results.push('SubPromise ctor called'); + super(executor); + } + then(res, rej) { + results.push('SubPromise#then called'); + return intermediatePromise = super.then(res, rej); + } +} + +let subPromise = new SubPromise(function(res, rej) { + results.push('SubPromise ctor called executor'); + res('result'); +}); + +let intermediatePromise; +let allSubPromise = SubPromise.all([subPromise]); + +assertEq(subPromise instanceof SubPromise, true); +assertEq(allSubPromise instanceof SubPromise, true); +assertEq(intermediatePromise instanceof SubPromise, true); + +expected = [ +'SubPromise ctor called', +'SubPromise ctor called executor', +'SubPromise ctor called', +'SubPromise#then called', +'SubPromise ctor called', +]; + +assertEq(results.length, expected.length); +expected.forEach((expected,i) => assertEq(results[i], expected)); + +subPromise.then(val=>results.push('subPromise.then with val ' + val)); +allSubPromise.then(val=>results.push('allSubPromise.then with val ' + val)); + +expected.forEach((expected,i) => assertEq(results[i], expected)); +expected = expected.concat([ +'SubPromise#then called', +'SubPromise ctor called', +'SubPromise#then called', +'SubPromise ctor called', +]); + +assertEq(results.length, expected.length); +expected.forEach((expected,i) => assertEq(results[i], expected)); + +drainJobQueue(); + +expected = expected.concat([ +'subPromise.then with val result', +'allSubPromise.then with val result', +]); + +assertEq(results.length, expected.length); + +this.reportCompare && reportCompare(0, 0, "ok"); diff --git a/js/src/tests/non262/Promise/self-resolve.js b/js/src/tests/non262/Promise/self-resolve.js new file mode 100644 index 0000000000..53f76a99ce --- /dev/null +++ b/js/src/tests/non262/Promise/self-resolve.js @@ -0,0 +1,40 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue + +// Resolve Promise with itself by directly calling the "Promise Resolve Function". +let resolve; +let promise = new Promise(function(x) { resolve = x; }); +resolve(promise) + +let results = []; +promise.then(res => assertEq(true, false, "not reached")).catch(res => { + assertEq(res instanceof TypeError, true); + results.push("rejected"); +}); + +drainJobQueue() + +assertEq(results.length, 1); +assertEq(results[0], "rejected"); + + +// Resolve Promise with itself when the "Promise Resolve Function" is called +// from (the fast path in) PromiseReactionJob. +results = []; + +promise = new Promise(x => { resolve = x; }); +let promise2 = promise.then(() => promise2); + +promise2.then(() => assertEq(true, false, "not reached"), res => { + assertEq(res instanceof TypeError, true); + results.push("rejected"); +}); + +resolve(); + +drainJobQueue(); + +assertEq(results.length, 1); +assertEq(results[0], "rejected"); + + +this.reportCompare && reportCompare(0, 0, "ok"); diff --git a/js/src/tests/non262/Promise/shell.js b/js/src/tests/non262/Promise/shell.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/js/src/tests/non262/Promise/shell.js diff --git a/js/src/tests/non262/Promise/withResolvers.js b/js/src/tests/non262/Promise/withResolvers.js new file mode 100644 index 0000000000..d689da767b --- /dev/null +++ b/js/src/tests/non262/Promise/withResolvers.js @@ -0,0 +1,25 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue + +{ + let {resolve, promise} = Promise.withResolvers(); + + let result = undefined; + promise.then((v) => result = v); + resolve(5); + + drainJobQueue(); + assertEq(result, 5); +} + +{ + let {reject, promise} = Promise.withResolvers(); + + let result = undefined; + promise.catch((v) => result = v); + reject("abc"); + + drainJobQueue(); + assertEq(result, "abc"); +} + +reportCompare(true,true); |