Skip to content

Commit 81e303c

Browse files
committed
fix(cli): Resolve esmock race condition in serve tests on Node 22
Replace fragile setTimeout(0) with deterministic promise-based signals to wait for the handler's async work to complete. On Node 22, esmock's off-thread ESM loader hooks can take more than one microtask tick to resolve dynamic imports, causing afterEach's esmock.purge() to nullify the mock cache before the handler's imports resolve. Use handlerReady (resolves on first stdout write) for most tests, and openCalled (resolves when the open stub fires) for --open tests which have an additional dynamic import after stdout output.
1 parent c8660ae commit 81e303c

1 file changed

Lines changed: 39 additions & 30 deletions

File tree

  • packages/cli/test/lib/cli/commands

packages/cli/test/lib/cli/commands/serve.js

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,13 @@ test.beforeEach(async (t) => {
6363

6464
t.context.consoleOutput = "";
6565
t.context.processStderrWrite = sinon.stub(process.stderr, "write").callsFake((message) => {
66-
// NOTE: This fake impl only supports one string arg passed to console.log
6766
t.context.consoleOutput += message;
6867
});
69-
t.context.processStdoutWrite = sinon.stub(process.stdout, "write").callsFake((message) => {
70-
// NOTE: This fake impl only supports one string arg passed to console.log
71-
t.context.consoleOutput += message;
68+
t.context.handlerReady = new Promise((resolve) => {
69+
t.context.processStdoutWrite = sinon.stub(process.stdout, "write").callsFake((message) => {
70+
t.context.consoleOutput += message;
71+
resolve();
72+
});
7273
});
7374

7475
t.context.open = sinon.stub();
@@ -90,7 +91,7 @@ test.serial("ui5 serve: default", async (t) => {
9091
const {argv, serve, graph, server, fakeGraph} = t.context;
9192

9293
serve.handler(argv);
93-
await new Promise((resolve) => setTimeout(resolve, 0));
94+
await t.context.handlerReady;
9495

9596
t.is(graph.graphFromStaticFile.callCount, 0);
9697
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -138,7 +139,7 @@ test.serial("ui5 serve --h2", async (t) => {
138139
argv.h2 = true;
139140

140141
serve.handler(argv);
141-
await new Promise((resolve) => setTimeout(resolve, 0));
142+
await t.context.handlerReady;
142143

143144
t.is(graph.graphFromStaticFile.callCount, 0);
144145
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -181,7 +182,7 @@ test.serial("ui5 serve --accept-remote-connections", async (t) => {
181182
argv.acceptRemoteConnections = true;
182183

183184
serve.handler(argv);
184-
await new Promise((resolve) => setTimeout(resolve, 0));
185+
await t.context.handlerReady;
185186

186187
t.is(graph.graphFromStaticFile.callCount, 0);
187188
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -221,12 +222,16 @@ URL: http://localhost:8080
221222
});
222223

223224
test.serial("ui5 serve --open", async (t) => {
224-
const {argv, serve, graph, server, fakeGraph, open} = t.context;
225+
const {argv, serve, graph, server, fakeGraph} = t.context;
226+
227+
const openCalled = new Promise((resolve) => {
228+
t.context.open.callsFake(resolve);
229+
});
225230

226231
argv.open = "index.html";
227232

228233
serve.handler(argv);
229-
await new Promise((resolve) => setTimeout(resolve, 0));
234+
await openCalled;
230235

231236
t.is(graph.graphFromStaticFile.callCount, 0);
232237
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -256,19 +261,23 @@ URL: http://localhost:8080
256261
}
257262
]);
258263

259-
t.is(open.callCount, 1);
260-
t.deepEqual(open.getCall(0).args, [
264+
t.is(t.context.open.callCount, 1);
265+
t.deepEqual(t.context.open.getCall(0).args, [
261266
"http://localhost:8080/index.html"
262267
]);
263268
});
264269

265270
test.serial("ui5 serve --open (opens default url)", async (t) => {
266-
const {argv, serve, graph, server, fakeGraph, open} = t.context;
271+
const {argv, serve, graph, server, fakeGraph} = t.context;
272+
273+
const openCalled = new Promise((resolve) => {
274+
t.context.open.callsFake(resolve);
275+
});
267276

268277
argv.open = true;
269278

270279
serve.handler(argv);
271-
await new Promise((resolve) => setTimeout(resolve, 0));
280+
await openCalled;
272281

273282
t.is(graph.graphFromStaticFile.callCount, 0);
274283
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -298,8 +307,8 @@ URL: http://localhost:8080
298307
}
299308
]);
300309

301-
t.is(open.callCount, 1);
302-
t.deepEqual(open.getCall(0).args, [
310+
t.is(t.context.open.callCount, 1);
311+
t.deepEqual(t.context.open.getCall(0).args, [
303312
"http://localhost:8080"
304313
]);
305314
});
@@ -311,7 +320,7 @@ test.serial("ui5 serve --config", async (t) => {
311320
argv.config = fakePath;
312321

313322
serve.handler(argv);
314-
await new Promise((resolve) => setTimeout(resolve, 0));
323+
await t.context.handlerReady;
315324

316325
t.is(graph.graphFromStaticFile.callCount, 0);
317326
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -349,7 +358,7 @@ test.serial("ui5 serve --dependency-definition", async (t) => {
349358
argv.dependencyDefinition = fakePath;
350359

351360
serve.handler(argv);
352-
await new Promise((resolve) => setTimeout(resolve, 0));
361+
await t.context.handlerReady;
353362

354363
t.is(graph.graphFromPackageDependencies.callCount, 0);
355364
t.is(graph.graphFromStaticFile.callCount, 1);
@@ -389,7 +398,7 @@ test.serial("ui5 serve --dependency-definition / --config", async (t) => {
389398
argv.config = fakeConfigPath;
390399

391400
serve.handler(argv);
392-
await new Promise((resolve) => setTimeout(resolve, 0));
401+
await t.context.handlerReady;
393402

394403
t.is(graph.graphFromPackageDependencies.callCount, 0);
395404
t.is(graph.graphFromStaticFile.callCount, 1);
@@ -425,7 +434,7 @@ test.serial("ui5 serve --framework-version", async (t) => {
425434
argv.frameworkVersion = "1.234.5";
426435

427436
serve.handler(argv);
428-
await new Promise((resolve) => setTimeout(resolve, 0));
437+
await t.context.handlerReady;
429438

430439
t.is(graph.graphFromStaticFile.callCount, 0);
431440
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -462,7 +471,7 @@ test.serial("ui5 serve --cache-mode", async (t) => {
462471
argv.cacheMode = "Force";
463472

464473
serve.handler(argv);
465-
await new Promise((resolve) => setTimeout(resolve, 0));
474+
await t.context.handlerReady;
466475

467476
t.is(graph.graphFromStaticFile.callCount, 0);
468477
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -499,7 +508,7 @@ test.serial("ui5 serve --workspace", async (t) => {
499508
argv.workspace = "dolphin";
500509

501510
serve.handler(argv);
502-
await new Promise((resolve) => setTimeout(resolve, 0));
511+
await t.context.handlerReady;
503512

504513
t.is(graph.graphFromStaticFile.callCount, 0);
505514
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -536,7 +545,7 @@ test.serial("ui5 serve --no-workspace", async (t) => {
536545
argv.workspace = false;
537546

538547
serve.handler(argv);
539-
await new Promise((resolve) => setTimeout(resolve, 0));
548+
await t.context.handlerReady;
540549

541550
t.is(graph.graphFromStaticFile.callCount, 0);
542551
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -574,7 +583,7 @@ test.serial("ui5 serve --workspace-config", async (t) => {
574583
argv.workspaceConfig = fakePath;
575584

576585
serve.handler(argv);
577-
await new Promise((resolve) => setTimeout(resolve, 0));
586+
await t.context.handlerReady;
578587

579588
t.is(graph.graphFromStaticFile.callCount, 0);
580589
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -611,7 +620,7 @@ test.serial("ui5 serve --sap-csp-policies", async (t) => {
611620
argv.sapCspPolicies = true;
612621

613622
serve.handler(argv);
614-
await new Promise((resolve) => setTimeout(resolve, 0));
623+
await t.context.handlerReady;
615624

616625
t.is(graph.graphFromStaticFile.callCount, 0);
617626
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -648,7 +657,7 @@ test.serial("ui5 serve --serve-csp-reports", async (t) => {
648657
argv.serveCspReports = true;
649658

650659
serve.handler(argv);
651-
await new Promise((resolve) => setTimeout(resolve, 0));
660+
await t.context.handlerReady;
652661

653662
t.is(graph.graphFromStaticFile.callCount, 0);
654663
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -685,7 +694,7 @@ test.serial("ui5 serve --simple-index", async (t) => {
685694
argv.simpleIndex = true;
686695

687696
serve.handler(argv);
688-
await new Promise((resolve) => setTimeout(resolve, 0));
697+
await t.context.handlerReady;
689698

690699
t.is(graph.graphFromStaticFile.callCount, 0);
691700
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -729,7 +738,7 @@ test.serial("ui5 serve with ui5.yaml port setting", async (t) => {
729738
});
730739

731740
serve.handler(argv);
732-
await new Promise((resolve) => setTimeout(resolve, 0));
741+
await t.context.handlerReady;
733742

734743
t.is(graph.graphFromStaticFile.callCount, 0);
735744
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -780,7 +789,7 @@ test.serial("ui5 serve --h2 with ui5.yaml port setting", async (t) => {
780789
argv.h2 = true;
781790

782791
serve.handler(argv);
783-
await new Promise((resolve) => setTimeout(resolve, 0));
792+
await t.context.handlerReady;
784793

785794
t.is(graph.graphFromStaticFile.callCount, 0);
786795
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -838,7 +847,7 @@ test.serial("ui5 serve --h2 with ui5.yaml port setting and port CLI argument", a
838847
argv.port = 5555;
839848

840849
serve.handler(argv);
841-
await new Promise((resolve) => setTimeout(resolve, 0));
850+
await t.context.handlerReady;
842851

843852
t.is(graph.graphFromStaticFile.callCount, 0);
844853
t.is(graph.graphFromPackageDependencies.callCount, 1);
@@ -879,7 +888,7 @@ test.serial("ui5 serve: Error callback propagates to handler", async (t) => {
879888
const {argv, serve} = t.context;
880889

881890
const handlerPromise = serve.handler(argv);
882-
await new Promise((resolve) => setTimeout(resolve, 0));
891+
await t.context.handlerReady;
883892

884893
t.context.serverErrorCallback(new Error("Server crashed"));
885894

0 commit comments

Comments
 (0)