Skip to content

Commit dad63f8

Browse files
committed
Fix generator throw semantics and Promise constructor name
- Fix generator `throw` regression where caught exceptions closed the generator. - Implement recursive yield checks in `find_first_yield_in_statements` for TryCatch blocks. - Set `Promise.name` to "Promise" in `initialize_promise`. - Update `make_promise_js_object` to link `Promise.prototype` correctly. - Propagate `env` through Promise helpers to ensure availability of global constructors. - Fix `ReferenceError` in internal resolution closures by setting prototype to global env. - Unignore various tests in `promise_tests.rs` and `promise_async_tests.rs`.
1 parent a946679 commit dad63f8

23 files changed

Lines changed: 4250 additions & 979 deletions

.github/workflows/rust.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ jobs:
4343
- name: run js scripts tests
4444
if: ${{ !cancelled() }}
4545
run: |
46-
# cargo run --all-features --example js -- js-scripts/async_n_throw_async_tests.js
46+
cargo run --all-features --example js -- js-scripts/async_n_throw_async_tests_regression.js
47+
cargo run --all-features --example js -- js-scripts/async_n_throw_async_tests.js
4748
cargo run --all-features --example js -- js-scripts/atomics_test.js
4849
cargo run --all-features --example js -- js-scripts/atomics_wait_notify_test.js
4950
cargo run --all-features --example js -- js-scripts/bigint_test.js

js-scripts/async_n_throw_async_tests.js

Lines changed: 102 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use strict";
2+
13
// Copyright (C) 2022 Igalia, S.L. All rights reserved.
24
// This code is governed by the BSD license found in the LICENSE file.
35
/*---
@@ -6,8 +8,37 @@ description: |
68
defines: [asyncTest, assert.throwsAsync]
79
---*/
810

9-
// Ensure a minimal `assert` object exists when running outside Test262
10-
if (typeof assert === 'undefined') var assert = {};
11+
// Ensure a minimal `assert` function exists when running outside Test262
12+
if (typeof assert === 'undefined') {
13+
var assert = function (actual, expected, message) {
14+
function deepEqual(a, b) {
15+
if (a === b) return true;
16+
if (typeof a === 'number' && typeof b === 'number' && isNaN(a) && isNaN(b)) return true;
17+
if (a && b && typeof a === 'object' && typeof b === 'object') {
18+
if (Array.isArray(a) && Array.isArray(b)) {
19+
if (a.length !== b.length) return false;
20+
for (var i = 0; i < a.length; i++) {
21+
if (!deepEqual(a[i], b[i])) return false;
22+
}
23+
return true;
24+
}
25+
var aKeys = Object.keys(a);
26+
var bKeys = Object.keys(b);
27+
if (aKeys.length !== bKeys.length) return false;
28+
for (var i = 0; i < aKeys.length; i++) {
29+
var k = aKeys[i];
30+
if (!b.hasOwnProperty(k) || !deepEqual(a[k], b[k])) return false;
31+
}
32+
return true;
33+
}
34+
return false;
35+
}
36+
37+
if (!deepEqual(actual, expected)) {
38+
throw new Test262Error(message || 'assertion failed');
39+
}
40+
};
41+
}
1142

1243
// Minimal Test262 shims used by helpers
1344
function Test262Error(message) {
@@ -33,25 +64,27 @@ function asyncTest(testFunc) {
3364
return;
3465
}
3566
try {
36-
testFunc().then(
37-
function () {
38-
$DONE();
39-
},
40-
function (error) {
41-
$DONE(error);
42-
}
43-
);
44-
} catch (syncError) {
45-
$DONE(syncError);
67+
console.log('asyncTest: invoking testFunc');
68+
var res = testFunc();
69+
console.log('asyncTest: testFunc result', res && res.constructor ? res.constructor.name : typeof res);
70+
res.then(
71+
function () {
72+
console.log('asyncTest: resolved, calling $DONE');
73+
$DONE();
74+
},
75+
function (error) {
76+
console.log('asyncTest: rejected, calling $DONE with', error);
77+
$DONE(error);
78+
}
79+
);
80+
} catch (syncError) {
81+
console.log('asyncTest: threw synchronously', syncError);
82+
$DONE(syncError);
83+
}
4684
}
47-
}
4885

4986
/**
5087
* Asserts that a callback asynchronously throws an instance of a particular
51-
* error (i.e., returns a promise whose rejection value is an object referencing
52-
* the constructor).
53-
*
54-
* @param {Function} expectedErrorConstructor the expected constructor of the
5588
* rejection value
5689
* @param {Function} func the callback
5790
* @param {string} [message] the prefix to use for failure messages
@@ -116,14 +149,45 @@ assert.throwsAsync = function (expectedErrorConstructor, func, message) {
116149

117150
// --- Test harness for running ---
118151
{
152+
// Install a persistent $DONE dispatcher to avoid handler overwrite races
153+
if (!globalThis.__done_dispatcher_installed) {
154+
(function () {
155+
var handlers = [];
156+
globalThis.$register_done = function (fn) {
157+
var id = handlers.length;
158+
handlers.push({ fn: fn, settled: false });
159+
return id;
160+
};
161+
globalThis.$unregister_done = function (id) {
162+
if (handlers[id]) handlers[id].settled = true;
163+
};
164+
globalThis.$DONE = function (err) {
165+
// Deliver the call to the earliest un-settled registered handler
166+
for (var i = 0; i < handlers.length; i++) {
167+
if (!handlers[i].settled) {
168+
handlers[i].settled = true;
169+
// Schedule handler invocation as a microtask to avoid deep synchronous
170+
// re-entrancy when handlers call $DONE recursively. This aligns with
171+
// Node's behavior and prevents stack overflow for many nested handlers.
172+
Promise.resolve().then(function() { try { handlers[i].fn(err); } catch (e) { /* swallow */ } });
173+
break;
174+
}
175+
}
176+
};
177+
})();
178+
globalThis.__done_dispatcher_installed = true;
179+
}
119180
// Run a single asyncTest and return a promise that resolves/rejects when $DONE is called
120181
// expectError: when true, the test is expected to call $DONE with an error
121182
function runSingleAsyncTest(name, testFunc, expectError) {
183+
console.log('runSingleAsyncTest: start ->', name);
122184
return new Promise((resolve) => {
123185
var settled = false;
124-
globalThis.$DONE = function (err) {
186+
var doneId = globalThis.$register_done(function (err) {
187+
console.log('runSingleAsyncTest: $DONE called for', name, 'err=', err);
125188
if (settled) return;
126189
settled = true;
190+
globalThis.$unregister_done(doneId);
127191
if (expectError) {
128192
if (err === undefined) {
129193
console.log('asyncTest:', name, '-> FAIL (expected error)');
@@ -141,7 +205,7 @@ assert.throwsAsync = function (expectedErrorConstructor, func, message) {
141205
resolve({name: name, ok: false, err});
142206
}
143207
}
144-
};
208+
});
145209

146210
try {
147211
asyncTest(testFunc);
@@ -201,6 +265,10 @@ assert.throwsAsync = function (expectedErrorConstructor, func, message) {
201265

202266
await runThrowTests();
203267

268+
for (let i = 0; i < results.length; i++) {
269+
results[i] = await Promise.resolve(results[i]);
270+
}
271+
console.log('DEBUG results:', results);
204272
const passed = results.filter(r => r.ok).length;
205273
const failed = results.length - passed;
206274
console.log('\nSummary:', passed, 'passed,', failed, 'failed');
@@ -218,3 +286,18 @@ assert.throwsAsync = function (expectedErrorConstructor, func, message) {
218286
assert(res, [1, [2, 3]], "Async function with rest");
219287
});
220288
}
289+
290+
console.log('=== All async_n_throw_async_tests.js tests setup done ===');
291+
292+
try {
293+
var p = Promise.resolve(1);
294+
console.log("Promise.name:", Promise.name);
295+
console.log("p.constructor === Promise:", p.constructor === Promise);
296+
console.log("p.constructor.name:", p.constructor.name);
297+
} catch (e) {
298+
console.log("Error:", e);
299+
}
300+
301+
console.log('=== Running async_n_throw_async_tests_regression.js ===');
302+
303+
return true;

0 commit comments

Comments
 (0)