diff --git a/harness/asyncHelpers.js b/harness/asyncHelpers.js index 2d55fafebc..4763838afc 100644 --- a/harness/asyncHelpers.js +++ b/harness/asyncHelpers.js @@ -3,7 +3,7 @@ /*--- description: | A collection of assertion and wrapper functions for testing asynchronous built-ins. -defines: [asyncTest] +defines: [asyncTest, assert.throwsAsync] ---*/ /** @@ -50,18 +50,21 @@ function asyncTest(testFunc) { */ assert.throwsAsync = function (expectedErrorConstructor, func, message) { return new Promise(function (resolve) { - var expectedName = expectedErrorConstructor.name; - var expectation = "Expected a " + expectedName + " to be thrown asynchronously"; var fail = function (detail) { if (message === undefined) { throw new Test262Error(detail); } throw new Test262Error(message + " " + detail); }; - var res; + if (typeof expectedErrorConstructor !== "function") { + fail("assert.throwsAsync called with an argument that is not an error constructor"); + } if (typeof func !== "function") { fail("assert.throwsAsync called with an argument that is not a function"); } + var expectedName = expectedErrorConstructor.name; + var expectation = "Expected a " + expectedName + " to be thrown asynchronously"; + var res; try { res = func(); } catch (thrown) { @@ -70,28 +73,33 @@ assert.throwsAsync = function (expectedErrorConstructor, func, message) { if (res === null || typeof res !== "object" || typeof res.then !== "function") { fail(expectation + " but result was not a thenable"); } - + var onResFulfilled, onResRejected; + var resSettlementP = new Promise(function (onFulfilled, onRejected) { + onResFulfilled = onFulfilled; + onResRejected = onRejected; + }); try { - resolve(Promise.resolve(res).then( - function () { - fail(expectation + " but no exception was thrown at all"); - }, - function (thrown) { - var actualName; - if (thrown === null || typeof thrown !== "object") { - fail(expectation + " but thrown value was not an object"); - } else if (thrown.constructor !== expectedErrorConstructor) { - actualName = thrown.constructor.name; - if (expectedName === actualName) { - fail(expectation + - " but got a different error constructor with the same name"); - } - fail(expectation + " but got a " + actualName); - } - } - )); + res.then(onResFulfilled, onResRejected) } catch (thrown) { fail(expectation + " but .then threw synchronously"); } + resolve(resSettlementP.then( + function () { + fail(expectation + " but no exception was thrown at all"); + }, + function (thrown) { + var actualName; + if (thrown === null || typeof thrown !== "object") { + fail(expectation + " but thrown value was not an object"); + } else if (thrown.constructor !== expectedErrorConstructor) { + actualName = thrown.constructor.name; + if (expectedName === actualName) { + fail(expectation + + " but got a different error constructor with the same name"); + } + fail(expectation + " but got a " + actualName); + } + } + )); }); }; diff --git a/test/harness/asyncHelpers-throwsAsync-func-never-settles.js b/test/harness/asyncHelpers-throwsAsync-func-never-settles.js new file mode 100644 index 0000000000..60f73766d1 --- /dev/null +++ b/test/harness/asyncHelpers-throwsAsync-func-never-settles.js @@ -0,0 +1,37 @@ +// Copyright (C) 2024 Julián Espina. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +description: | + assert.throwsAsync returns a promise that never settles if func returns a thenable that never settles. +flags: [async] +includes: [asyncHelpers.js] +---*/ + +var realDone = $DONE; +var doneCalls = 0 +globalThis.$DONE = function () { + doneCalls++; +} + +function delay() { + var later = Promise.resolve(); + for (var i = 0; i < 100; i++) { + later = later.then(); + } + return later; +} + +(async function () { + // Spy on the promise returned by an invocation of assert.throwsAsync + // with a function that returns a thenable which never settles. + var neverSettlingThenable = { then: function () { } }; + const p = assert.throwsAsync(TypeError, function () { return neverSettlingThenable }); + assert(p instanceof Promise, "assert.throwsAsync should return a promise"); + p.then($DONE, $DONE); +})() + // Give it a long time to try. + .then(delay, delay) + .then(function () { + assert.sameValue(doneCalls, 0, "$DONE should not have been called") + }) + .then(realDone, realDone);