From 2972c95649f51aad826e93e2a4def7dc92b8acb9 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Wed, 8 Apr 2026 11:52:06 -0700 Subject: [PATCH 1/8] Fix WebSocket test double-done flake When the WebSocket connection fails, onerror fires followed by onclose. Both called done(), causing mocha's 'done() called multiple times' error. Guard done() with a finished flag so only the first callback completes the test. This fixes the intermittent CI flake on Win32_x64_JSI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Tests/UnitTests/Scripts/tests.ts | 41 ++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/Tests/UnitTests/Scripts/tests.ts b/Tests/UnitTests/Scripts/tests.ts index a79ebdf0..6c99606e 100644 --- a/Tests/UnitTests/Scripts/tests.ts +++ b/Tests/UnitTests/Scripts/tests.ts @@ -408,6 +408,14 @@ if (hostPlatform !== "Unix") { it("should connect correctly with one websocket connection", function (done) { const ws = new WebSocket("wss://ws.postman-echo.com/raw"); const testMessage = "testMessage"; + let finished = false; + const finish = (err?: Error) => { + if (!finished) { + finished = true; + done(err); + } + }; + ws.onopen = () => { try { expect(ws).to.have.property("readyState", 1); @@ -415,7 +423,7 @@ if (hostPlatform !== "Unix") { ws.send(testMessage); } catch (e) { - done(e); + finish(e as Error); } }; @@ -425,28 +433,35 @@ if (hostPlatform !== "Unix") { ws.close(); } catch (e) { - done(e); + finish(e as Error); } }; ws.onclose = () => { try { expect(ws).to.have.property("readyState", 3); - done(); + finish(); } catch (e) { - done(e); + finish(e as Error); } }; ws.onerror = (ev) => { - done(new Error("WebSocket failed")); + finish(new Error("WebSocket failed")); }; }); it("should connect correctly with multiple websocket connections", function (done) { const testMessage1 = "testMessage1"; const testMessage2 = "testMessage2"; + let finished = false; + const finish = (err?: Error) => { + if (!finished) { + finished = true; + done(err); + } + }; const ws1 = new WebSocket("wss://ws.postman-echo.com/raw"); ws1.onopen = () => { @@ -458,7 +473,7 @@ if (hostPlatform !== "Unix") { ws2.send(testMessage2); } catch (e) { - done(e); + finish(e as Error); } }; @@ -468,7 +483,7 @@ if (hostPlatform !== "Unix") { ws2.close(); } catch (e) { - done(e); + finish(e as Error); } }; @@ -478,12 +493,12 @@ if (hostPlatform !== "Unix") { ws1.send(testMessage1); } catch (e) { - done(e); + finish(e as Error); } }; ws2.onerror = (ev) => { - done(new Error("Websocket failed")); + finish(new Error("Websocket failed")); }; } @@ -493,22 +508,22 @@ if (hostPlatform !== "Unix") { ws1.close(); } catch (e) { - done(e); + finish(e as Error); } } ws1.onclose = () => { try { expect(ws1).to.have.property("readyState", 3); - done(); + finish(); } catch (e) { - done(e); + finish(e as Error); } } ws1.onerror = (ev) => { - done(new Error("Websocket failed")); + finish(new Error("Websocket failed")); }; }); From 0310406b535275aad0f2db2a3a042555374f266b Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Thu, 9 Apr 2026 08:10:16 -0700 Subject: [PATCH 2/8] Fix inconsistent WebSocket capitalization in error messages Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Tests/UnitTests/Scripts/tests.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/UnitTests/Scripts/tests.ts b/Tests/UnitTests/Scripts/tests.ts index 6c99606e..b64d70f5 100644 --- a/Tests/UnitTests/Scripts/tests.ts +++ b/Tests/UnitTests/Scripts/tests.ts @@ -498,7 +498,7 @@ if (hostPlatform !== "Unix") { }; ws2.onerror = (ev) => { - finish(new Error("Websocket failed")); + finish(new Error("WebSocket failed")); }; } @@ -523,7 +523,7 @@ if (hostPlatform !== "Unix") { } ws1.onerror = (ev) => { - finish(new Error("Websocket failed")); + finish(new Error("WebSocket failed")); }; }); From a516b5dd161409585efca0ef1188ad8406abb85d Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Thu, 9 Apr 2026 17:18:51 -0700 Subject: [PATCH 3/8] Retrigger CI From 981dbe963aee5d763ea2b36ff38688ee4f878899 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Fri, 10 Apr 2026 14:06:39 -0700 Subject: [PATCH 4/8] Bump UrlLib/AndroidExtensions and update WebSocket tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump UrlLib to 880c2575e57ca0b59068ecc4860f185b9970e0ce (onClose after onError on all platforms) and AndroidExtensions to 2e85a8d43b89246c460112c9e5546ad54b6e87b4 (remove redundant errorCallback). With onclose guaranteed as the terminal event, tests now collect errors from earlier phases and report them in onclose — done() is called exactly once. The invalid domain test now verifies onerror fires before onclose. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 4 +- Tests/UnitTests/Scripts/tests.ts | 70 ++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 116efa6b..b385b614 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ include(FetchContent) # -------------------------------------------------- FetchContent_Declare(AndroidExtensions GIT_REPOSITORY https://github.com/BabylonJS/AndroidExtensions.git - GIT_TAG 2d5af72259cc73e5f249d3c99bee2010be9cb042 + GIT_TAG 2e85a8d43b89246c460112c9e5546ad54b6e87b4 EXCLUDE_FROM_ALL) FetchContent_Declare(arcana.cpp GIT_REPOSITORY https://github.com/microsoft/arcana.cpp.git @@ -37,7 +37,7 @@ FetchContent_Declare(llhttp EXCLUDE_FROM_ALL) FetchContent_Declare(UrlLib GIT_REPOSITORY https://github.com/BabylonJS/UrlLib.git - GIT_TAG d53beb958b1cccafd5411260ace6d32b68b56a83 + GIT_TAG 880c2575e57ca0b59068ecc4860f185b9970e0ce EXCLUDE_FROM_ALL) # -------------------------------------------------- diff --git a/Tests/UnitTests/Scripts/tests.ts b/Tests/UnitTests/Scripts/tests.ts index b64d70f5..38990703 100644 --- a/Tests/UnitTests/Scripts/tests.ts +++ b/Tests/UnitTests/Scripts/tests.ts @@ -408,13 +408,7 @@ if (hostPlatform !== "Unix") { it("should connect correctly with one websocket connection", function (done) { const ws = new WebSocket("wss://ws.postman-echo.com/raw"); const testMessage = "testMessage"; - let finished = false; - const finish = (err?: Error) => { - if (!finished) { - finished = true; - done(err); - } - }; + let error: Error | undefined; ws.onopen = () => { try { @@ -423,7 +417,7 @@ if (hostPlatform !== "Unix") { ws.send(testMessage); } catch (e) { - finish(e as Error); + error = e as Error; } }; @@ -433,35 +427,35 @@ if (hostPlatform !== "Unix") { ws.close(); } catch (e) { - finish(e as Error); + error = e as Error; } }; + // onclose is always the terminal event. + // Collect errors from earlier phases and report them here. ws.onclose = () => { + if (error) { + done(error); + return; + } try { expect(ws).to.have.property("readyState", 3); - finish(); + done(); } catch (e) { - finish(e as Error); + done(e); } }; - ws.onerror = (ev) => { - finish(new Error("WebSocket failed")); + ws.onerror = () => { + error = new Error("WebSocket failed"); }; }); it("should connect correctly with multiple websocket connections", function (done) { const testMessage1 = "testMessage1"; const testMessage2 = "testMessage2"; - let finished = false; - const finish = (err?: Error) => { - if (!finished) { - finished = true; - done(err); - } - }; + let error: Error | undefined; const ws1 = new WebSocket("wss://ws.postman-echo.com/raw"); ws1.onopen = () => { @@ -473,7 +467,7 @@ if (hostPlatform !== "Unix") { ws2.send(testMessage2); } catch (e) { - finish(e as Error); + error = e as Error; } }; @@ -483,7 +477,7 @@ if (hostPlatform !== "Unix") { ws2.close(); } catch (e) { - finish(e as Error); + error = e as Error; } }; @@ -493,12 +487,12 @@ if (hostPlatform !== "Unix") { ws1.send(testMessage1); } catch (e) { - finish(e as Error); + error = e as Error; } }; - ws2.onerror = (ev) => { - finish(new Error("WebSocket failed")); + ws2.onerror = () => { + error = new Error("WebSocket failed"); }; } @@ -508,22 +502,26 @@ if (hostPlatform !== "Unix") { ws1.close(); } catch (e) { - finish(e as Error); + error = e as Error; } } ws1.onclose = () => { + if (error) { + done(error); + return; + } try { expect(ws1).to.have.property("readyState", 3); - finish(); + done(); } catch (e) { - finish(e as Error); + done(e); } } - ws1.onerror = (ev) => { - finish(new Error("WebSocket failed")); + ws1.onerror = () => { + error = new Error("WebSocket failed"); }; }); @@ -538,8 +536,18 @@ if (hostPlatform !== "Unix") { it("should trigger error callback with invalid domain", function (done) { this.timeout(10000); const ws = new WebSocket("wss://example"); + let errorFired = false; ws.onerror = () => { - done(); + errorFired = true; + }; + ws.onclose = () => { + try { + expect(errorFired).to.be.true; + done(); + } + catch (e) { + done(e); + } }; }); }) From 751ca89a309c20012f250055275b8ee51e2d19ec Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Mon, 13 Apr 2026 13:33:55 -0700 Subject: [PATCH 5/8] Simplify WebSocket tests: remove try/catch and expect Replace expect() assertions with plain if-checks so async callbacks don't need try/catch. Each test calls done() exactly once from onclose (the terminal event). Errors from onerror or message validation are collected and passed through done(error). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Tests/UnitTests/Scripts/tests.ts | 87 ++++++-------------------------- Tests/package-lock.json | 14 +++++ 2 files changed, 29 insertions(+), 72 deletions(-) diff --git a/Tests/UnitTests/Scripts/tests.ts b/Tests/UnitTests/Scripts/tests.ts index 88bca26e..aa9c5621 100644 --- a/Tests/UnitTests/Scripts/tests.ts +++ b/Tests/UnitTests/Scripts/tests.ts @@ -410,40 +410,18 @@ if (hostPlatform !== "Unix") { let error: Error | undefined; ws.onopen = () => { - try { - expect(ws).to.have.property("readyState", 1); - expect(ws).to.have.property("url", "wss://ws.postman-echo.com/raw"); - ws.send(testMessage); - } - catch (e) { - error = e as Error; - } + ws.send(testMessage); }; ws.onmessage = (msg) => { - try { - expect(msg.data).to.equal(testMessage); - ws.close(); - } - catch (e) { - error = e as Error; + if (msg.data !== testMessage) { + error = new Error(`Expected "${testMessage}" but got "${msg.data}"`); } + ws.close(); }; - // onclose is always the terminal event. - // Collect errors from earlier phases and report them here. ws.onclose = () => { - if (error) { - done(error); - return; - } - try { - expect(ws).to.have.property("readyState", 3); - done(); - } - catch (e) { - done(e); - } + done(error); }; ws.onerror = () => { @@ -460,34 +438,18 @@ if (hostPlatform !== "Unix") { ws1.onopen = () => { const ws2 = new WebSocket("wss://ws.postman-echo.com/raw"); ws2.onopen = () => { - try { - expect(ws2).to.have.property("readyState", 1); - expect(ws2).to.have.property("url", "wss://ws.postman-echo.com/raw"); - ws2.send(testMessage2); - } - catch (e) { - error = e as Error; - } + ws2.send(testMessage2); }; ws2.onmessage = (msg) => { - try { - expect(msg.data).to.equal(testMessage2); - ws2.close(); - } - catch (e) { - error = e as Error; + if (msg.data !== testMessage2) { + error = new Error(`Expected "${testMessage2}" but got "${msg.data}"`); } + ws2.close(); }; ws2.onclose = () => { - try { - expect(ws2).to.have.property("readyState", 3); - ws1.send(testMessage1); - } - catch (e) { - error = e as Error; - } + ws1.send(testMessage1); }; ws2.onerror = () => { @@ -496,27 +458,14 @@ if (hostPlatform !== "Unix") { } ws1.onmessage = (msg) => { - try { - expect(msg.data).to.equal(testMessage1); - ws1.close(); - } - catch (e) { - error = e as Error; + if (msg.data !== testMessage1) { + error = new Error(`Expected "${testMessage1}" but got "${msg.data}"`); } + ws1.close(); } ws1.onclose = () => { - if (error) { - done(error); - return; - } - try { - expect(ws1).to.have.property("readyState", 3); - done(); - } - catch (e) { - done(e); - } + done(error); } ws1.onerror = () => { @@ -540,13 +489,7 @@ if (hostPlatform !== "Unix") { errorFired = true; }; ws.onclose = () => { - try { - expect(errorFired).to.be.true; - done(); - } - catch (e) { - done(e); - } + done(errorFired ? undefined : new Error("Expected onerror before onclose")); }; }); }) diff --git a/Tests/package-lock.json b/Tests/package-lock.json index e627a786..c343d3e7 100644 --- a/Tests/package-lock.json +++ b/Tests/package-lock.json @@ -3380,6 +3380,20 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", From c04de556317c153046159d5559202d06b7489585 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Mon, 13 Apr 2026 13:43:09 -0700 Subject: [PATCH 6/8] Restore expect assertions with ws.close() in catch blocks Keep expect() for all property and value checks. Errors are collected in an 'error' variable (typed unknown) and reported via done(error) in onclose. Catch blocks call ws.close() to ensure onclose fires promptly on assertion failure instead of waiting for timeout. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Tests/UnitTests/Scripts/tests.ts | 85 +++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/Tests/UnitTests/Scripts/tests.ts b/Tests/UnitTests/Scripts/tests.ts index aa9c5621..cffe0060 100644 --- a/Tests/UnitTests/Scripts/tests.ts +++ b/Tests/UnitTests/Scripts/tests.ts @@ -1,4 +1,4 @@ -import * as Mocha from "mocha"; +import * as Mocha from "mocha"; import { expect } from "chai"; Mocha.setup('bdd'); @@ -407,20 +407,39 @@ if (hostPlatform !== "Unix") { it("should connect correctly with one websocket connection", function (done) { const ws = new WebSocket("wss://ws.postman-echo.com/raw"); const testMessage = "testMessage"; - let error: Error | undefined; + let error: unknown; ws.onopen = () => { - ws.send(testMessage); + try { + expect(ws).to.have.property("readyState", 1); + expect(ws).to.have.property("url", "wss://ws.postman-echo.com/raw"); + ws.send(testMessage); + } + catch (e) { + error = e; + ws.close(); + } }; ws.onmessage = (msg) => { - if (msg.data !== testMessage) { - error = new Error(`Expected "${testMessage}" but got "${msg.data}"`); + try { + expect(msg.data).to.equal(testMessage); + } + catch (e) { + error = e; } ws.close(); }; ws.onclose = () => { + if (!error) { + try { + expect(ws).to.have.property("readyState", 3); + } + catch (e) { + error = e; + } + } done(error); }; @@ -432,24 +451,47 @@ if (hostPlatform !== "Unix") { it("should connect correctly with multiple websocket connections", function (done) { const testMessage1 = "testMessage1"; const testMessage2 = "testMessage2"; - let error: Error | undefined; + let error: unknown; const ws1 = new WebSocket("wss://ws.postman-echo.com/raw"); ws1.onopen = () => { const ws2 = new WebSocket("wss://ws.postman-echo.com/raw"); ws2.onopen = () => { - ws2.send(testMessage2); + try { + expect(ws2).to.have.property("readyState", 1); + expect(ws2).to.have.property("url", "wss://ws.postman-echo.com/raw"); + ws2.send(testMessage2); + } + catch (e) { + error = e; + ws2.close(); + } }; ws2.onmessage = (msg) => { - if (msg.data !== testMessage2) { - error = new Error(`Expected "${testMessage2}" but got "${msg.data}"`); + try { + expect(msg.data).to.equal(testMessage2); + } + catch (e) { + error = e; } ws2.close(); }; ws2.onclose = () => { - ws1.send(testMessage1); + if (!error) { + try { + expect(ws2).to.have.property("readyState", 3); + ws1.send(testMessage1); + } + catch (e) { + error = e; + ws1.close(); + } + } + else { + ws1.close(); + } }; ws2.onerror = () => { @@ -458,13 +500,24 @@ if (hostPlatform !== "Unix") { } ws1.onmessage = (msg) => { - if (msg.data !== testMessage1) { - error = new Error(`Expected "${testMessage1}" but got "${msg.data}"`); + try { + expect(msg.data).to.equal(testMessage1); + } + catch (e) { + error = e; } ws1.close(); } ws1.onclose = () => { + if (!error) { + try { + expect(ws1).to.have.property("readyState", 3); + } + catch (e) { + error = e; + } + } done(error); } @@ -489,7 +542,13 @@ if (hostPlatform !== "Unix") { errorFired = true; }; ws.onclose = () => { - done(errorFired ? undefined : new Error("Expected onerror before onclose")); + try { + expect(errorFired).to.be.true; + done(); + } + catch (e) { + done(e); + } }; }); }) From 0965b315ad71e768c3dc28367297d09c85df92e9 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Mon, 13 Apr 2026 13:51:20 -0700 Subject: [PATCH 7/8] Revert accidental package-lock.json change Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Tests/package-lock.json | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Tests/package-lock.json b/Tests/package-lock.json index c343d3e7..e627a786 100644 --- a/Tests/package-lock.json +++ b/Tests/package-lock.json @@ -3380,20 +3380,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", From bd5dcff5536a36d19e9129eae6be8058755a35d6 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Mon, 13 Apr 2026 14:35:35 -0700 Subject: [PATCH 8/8] Retrigger CI