Skip to content

Commit ca6d8ca

Browse files
committed
Merge remote-tracking branch 'origin/main' into flash-via-usb
2 parents dde29ab + 6681410 commit ca6d8ca

2 files changed

Lines changed: 26 additions & 23 deletions

File tree

src/util/post-install-logs.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -221,15 +221,10 @@ async function openLiveSerialPort(
221221
if (name === "InvalidStateError" && /already open/i.test(message)) {
222222
return p;
223223
}
224-
// NetworkError means the device is still gone mid-re-enumeration: keep
225-
// retrying the next candidate / round. Anything else (claimed by
226-
// another app, driver / security error) won't fix itself by waiting —
227-
// fail fast rather than stall the whole window behind a misleading
228-
// "still restarting" message.
229-
if (name !== "NetworkError") {
230-
console.error("[Web Serial] Failed to reopen port for logs:", err);
231-
return null;
232-
}
224+
// Unusable this round — still re-enumerating (NetworkError), claimed by
225+
// another app, or a transient driver / security error. Fall through to
226+
// the next candidate (including the cached handle this function exists
227+
// to fall back to) and only give up at the deadline below.
233228
}
234229
}
235230
if (Date.now() >= deadline) {

test/util/post-install-logs.test.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ function openPort(
3838
} as unknown as SerialPort;
3939
}
4040

41-
// A closed port whose open() always rejects. Defaults to a non-NetworkError
42-
// (the reopen bails fast); pass a NetworkError to exercise the retry window.
41+
// A closed port whose open() always rejects. Defaults to a non-NetworkError;
42+
// the reopen falls through to the next candidate either way, so pass a
43+
// NetworkError only when a test cares about the error kind.
4344
function deadPort(
4445
error: unknown = new DOMException("blocked", "SecurityError")
4546
): SerialPort {
@@ -216,21 +217,28 @@ describe("attachSerialLogStream reopen", () => {
216217
}
217218
});
218219

219-
it("fails fast on a non-recoverable open error (no waiting out the window)", async () => {
220-
const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
221-
const restore = withGetPorts(async () => []);
220+
it("falls through a non-NetworkError fresh handle to the cached port", async () => {
221+
// A re-enumerated handle that fails non-NetworkError (e.g. SecurityError on
222+
// a phantom UART bridge) must not abandon the cached fallback the loop
223+
// exists to provide. Resolves on the first round, so no fake timers.
224+
const cached = {
225+
readable: null,
226+
getInfo: () => ({ usbVendorId: 0x303a, usbProductId: 0x1001 }),
227+
open: vi.fn().mockResolvedValue(undefined),
228+
setSignals: vi.fn().mockResolvedValue(undefined),
229+
} as unknown as SerialPort;
230+
const restore = withGetPorts(async () => [
231+
deadPort(new DOMException("blocked", "SecurityError")),
232+
]);
222233
const dialog = stubDialog();
223234
try {
224-
// SecurityError won't fix itself by waiting — bail without fake timers.
225-
await attachSerialLogStream(deadPort(), dialog as never, defaultLocalize, 115200);
226-
expect(dialog.setSerialOpenFailed).toHaveBeenCalledTimes(1);
227-
expect(dialog.setSerialOpenFailed.mock.calls[0][0] as string).toContain(
228-
"USB 303a:1001"
229-
);
230-
expect(toastError).toHaveBeenCalledTimes(1);
231-
expect(errSpy).toHaveBeenCalled();
235+
await attachSerialLogStream(cached, dialog as never, defaultLocalize, 115200);
236+
expect(cached.open).toHaveBeenCalledWith({ baudRate: 115200 });
237+
expect(dialog.setSerialStream).toHaveBeenCalledTimes(1);
238+
expect(dialog.setSerialStream.mock.calls[0][0]).toBe(cached);
239+
expect(dialog.setSerialOpenFailed).not.toHaveBeenCalled();
240+
expect(toastError).not.toHaveBeenCalled();
232241
} finally {
233-
errSpy.mockRestore();
234242
restore();
235243
}
236244
});

0 commit comments

Comments
 (0)