Skip to content

Commit 697229d

Browse files
Add tests for resolution order of async modules promises
1 parent 2e74125 commit 697229d

12 files changed

+149
-0
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (C) 2025 Igalia, S.L. All rights reserved.
2+
// This code is governed by the BSD license found in the LICENSE file.
3+
/*---
4+
esid: sec-async-module-execution-fulfilled
5+
description: >
6+
When an async module fulfills, the promises relative to itself and its ancestors are resolved in leaf-to-root order
7+
info: |
8+
AsyncModuleExecutionFulfilled ( module )
9+
...
10+
7. If module.[[TopLevelCapability]] is not empty, then
11+
a. Assert: module.[[CycleRoot]] and module are the same Module Record.
12+
b. Perform ! Call(module.[[TopLevelCapability]].[[Resolve]], undefined, « undefined »).
13+
8. Let execList be a new empty List.
14+
9. Perform GatherAvailableAncestors(module, execList).
15+
10. Assert: All elements of execList have their [[AsyncEvaluationOrder]] field set to an integer, [[PendingAsyncDependencies]] field set to 0, and [[EvaluationError]] field set to empty.
16+
11. Let sortedExecList be a List whose elements are the elements of execList, sorted by their [[AsyncEvaluationOrder]] field in ascending order.
17+
12. For each Cyclic Module Record m of sortedExecList, do
18+
a. If m.[[Status]] is evaluated, then
19+
i. Assert: m.[[EvaluationError]] is not empty.
20+
b. Else if m.[[HasTLA]] is true, then
21+
i. Perform ExecuteAsyncModule(m).
22+
c. Else,
23+
i. Let result be m.ExecuteModule().
24+
ii. If result is an abrupt completion, then
25+
1. Perform AsyncModuleExecutionRejected(m, result.[[Value]]).
26+
iii. Else,
27+
1. Set m.[[AsyncEvaluationOrder]] to done.
28+
2. Set m.[[Status]] to evaluated.
29+
3. If m.[[TopLevelCapability]] is not empty, then
30+
a. Assert: m.[[CycleRoot]] and m are the same Module Record.
31+
b. Perform ! Call(m.[[TopLevelCapability]].[[Resolve]], undefined, « undefined »).
32+
flags: [module, async]
33+
features: [top-level-await, promise-with-resolvers]
34+
includes: [compareArray.js]
35+
---*/
36+
37+
import { p1, pA_start, pB_start } from "./fulfillment-order_setup_FIXTURE.js";
38+
39+
let logs = [];
40+
41+
const importsP = Promise.all([
42+
// Ensure that a.Evaluate() is called after b.Evaluate()
43+
pB_start.promise.then(() => import("./fulfillment-order_a_FIXTURE.js").finally(() => logs.push("A"))).catch(() => {}),
44+
import("./fulfillment-order_b_FIXTURE.js").finally(() => logs.push("B")).catch(() => {}),
45+
]);
46+
47+
// Wait for evaluation of both graphs with entyrpoints in A and B to start before
48+
// settling the promise that B is blocked on.
49+
Promise.all([pA_start.promise, pB_start.promise]).then(p1.resolve);
50+
51+
importsP.then(() => {
52+
assert.compareArray(logs, ["B", "A"]);
53+
54+
$DONE();
55+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright (C) 2025 Igalia, S.L. All rights reserved.
2+
// This code is governed by the BSD license found in the LICENSE file.
3+
4+
import { pA_start } from "./fulfillment-order_setup_FIXTURE.js";
5+
pA_start.resolve();
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright (C) 2025 Igalia, S.L. All rights reserved.
2+
// This code is governed by the BSD license found in the LICENSE file.
3+
4+
import "./fulfillment-order_a-sentinel_FIXTURE.js"; // Signal that evaluation of a's subgraph has started
5+
import "./fulfillment-order_b_FIXTURE.js";
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright (C) 2025 Igalia, S.L. All rights reserved.
2+
// This code is governed by the BSD license found in the LICENSE file.
3+
4+
import { pB_start } from "./fulfillment-order_setup_FIXTURE.js";
5+
pB_start.resolve();
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (C) 2025 Igalia, S.L. All rights reserved.
2+
// This code is governed by the BSD license found in the LICENSE file.
3+
4+
import "./fulfillment-order_b-sentinel_FIXTURE.js"; // Signal that evaluation of b's subgraph has started
5+
6+
import { p1 } from "./fulfillment-order_setup_FIXTURE.js";
7+
await p1.promise;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright (C) 2025 Igalia, S.L. All rights reserved.
2+
// This code is governed by the BSD license found in the LICENSE file.
3+
4+
export const p1 = Promise.withResolvers();
5+
export const pA_start = Promise.withResolvers();
6+
export const pB_start = Promise.withResolvers();
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (C) 2025 Igalia, S.L. All rights reserved.
2+
// This code is governed by the BSD license found in the LICENSE file.
3+
/*---
4+
esid: sec-async-module-execution-rejected
5+
description: >
6+
When an async module rejects, the promises relative to itself and its ancestors are resolved in leaf-to-root order
7+
info: |
8+
AsyncModuleExecutionRejected ( module, error )
9+
...
10+
9. If module.[[TopLevelCapability]] is not empty, then
11+
a. Assert: module.[[CycleRoot]] and module are the same Module Record.
12+
b. Perform ! Call(module.[[TopLevelCapability]].[[Reject]], undefined, « error »).
13+
10. For each Cyclic Module Record m of module.[[AsyncParentModules]], do
14+
a. Perform AsyncModuleExecutionRejected(m, error).
15+
flags: [module, async]
16+
features: [top-level-await, promise-with-resolvers]
17+
includes: [compareArray.js]
18+
---*/
19+
20+
import { p1, pA_start, pB_start } from "./rejection-order_setup_FIXTURE.js";
21+
22+
let logs = [];
23+
24+
const importsP = Promise.all([
25+
// Ensure that a.Evaluate() is called after b.Evaluate()
26+
pB_start.promise.then(() => import("./rejection-order_a_FIXTURE.js").finally(() => logs.push("A"))).catch(() => {}),
27+
import("./rejection-order_b_FIXTURE.js").finally(() => logs.push("B")).catch(() => {}),
28+
]);
29+
30+
// Wait for evaluation of both graphs with entyrpoints in A and B to start before
31+
// rejecting the promise that B is blocked on.
32+
Promise.all([pA_start.promise, pB_start.promise]).then(p1.reject);
33+
34+
importsP.then(() => {
35+
assert.compareArray(logs, ["B", "A"]);
36+
37+
$DONE();
38+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright (C) 2025 Igalia, S.L. All rights reserved.
2+
// This code is governed by the BSD license found in the LICENSE file.
3+
4+
import { pA_start } from "./rejection-order_setup_FIXTURE.js";
5+
pA_start.resolve();
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright (C) 2025 Igalia, S.L. All rights reserved.
2+
// This code is governed by the BSD license found in the LICENSE file.
3+
4+
import "./rejection-order_a-sentinel_FIXTURE.js"; // Signal that evaluation of a's subgraph has started
5+
import "./rejection-order_b_FIXTURE.js";
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright (C) 2025 Igalia, S.L. All rights reserved.
2+
// This code is governed by the BSD license found in the LICENSE file.
3+
4+
import { pB_start } from "./rejection-order_setup_FIXTURE.js";
5+
pB_start.resolve();

0 commit comments

Comments
 (0)