fix(pi): arrancar sin bridge deja de fallar en silencio + test del reinicio
startBoard llamaba `getBoardBridge(id)?.connect()`: si la placa no tenia bridge, el encadenamiento opcional se lo tragaba y el usuario se quedaba sin guest, sin error y con la barra mostrando Stop. Ahora avisa por consola y baja el flag de running, que es lo unico honesto que se puede hacer ahi. Test nuevo del camino que usa el boton de modo Linux: fijar el modo, parar y arrancar tiene que abrir el WebSocket del guest, dejar engineMode en linux y running en true; y un socket en CLOSING no puede impedir la reconexion.
This commit is contained in:
parent
6bfeaf1b15
commit
d98617f1d2
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* Turning on Linux mode must actually boot the guest.
|
||||
*
|
||||
* The button pins the mode and restarts the board: stopBoard, then
|
||||
* startBoard a moment later. That second call has to reach the board's
|
||||
* bridge and open its WebSocket — when it silently did not, the user got
|
||||
* no guest, no error, and a toolbar still showing Stop.
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
|
||||
// The bridge opens a real WebSocket; capture the instances instead.
|
||||
const opened: string[] = [];
|
||||
class FakeSocket {
|
||||
static OPEN = 1;
|
||||
static CONNECTING = 0;
|
||||
static CLOSING = 2;
|
||||
static CLOSED = 3;
|
||||
readyState = 0;
|
||||
onopen: (() => void) | null = null;
|
||||
onclose: (() => void) | null = null;
|
||||
onerror: (() => void) | null = null;
|
||||
onmessage: ((e: { data: string }) => void) | null = null;
|
||||
sent: string[] = [];
|
||||
constructor(public url: string) {
|
||||
opened.push(url);
|
||||
}
|
||||
send(d: string) {
|
||||
this.sent.push(d);
|
||||
}
|
||||
close() {
|
||||
this.readyState = FakeSocket.CLOSING;
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
opened.length = 0;
|
||||
(globalThis as unknown as { WebSocket: unknown }).WebSocket = FakeSocket;
|
||||
});
|
||||
|
||||
describe('Linux mode restart', () => {
|
||||
it('opens the guest WebSocket when the board is pinned to linux', async () => {
|
||||
const { useSimulatorStore, getBoardBridge } = await import(
|
||||
'../store/useSimulatorStore'
|
||||
);
|
||||
|
||||
const id = useSimulatorStore.getState().addBoard('raspberry-pi-3', 10, 10);
|
||||
expect(getBoardBridge(id), 'the board must get a bridge on add').toBeTruthy();
|
||||
|
||||
// What the Linux-mode button does: pin, stop, start.
|
||||
useSimulatorStore.getState().updateBoard(id, { enginePinned: 'linux' });
|
||||
useSimulatorStore.getState().stopBoard(id);
|
||||
useSimulatorStore.getState().startBoard(id);
|
||||
|
||||
expect(opened.filter((u) => u.includes('/simulation/ws/')).length).toBe(1);
|
||||
const board = useSimulatorStore.getState().boards.find((b) => b.id === id);
|
||||
expect(board?.engineMode).toBe('linux');
|
||||
expect(board?.running, 'the toolbar must show a running board').toBe(true);
|
||||
});
|
||||
|
||||
it('reconnects even when the previous socket is still closing', async () => {
|
||||
const { useSimulatorStore, getBoardBridge } = await import(
|
||||
'../store/useSimulatorStore'
|
||||
);
|
||||
const id = useSimulatorStore.getState().addBoard('raspberry-pi-4', 10, 10);
|
||||
const bridge = getBoardBridge(id)!;
|
||||
|
||||
useSimulatorStore.getState().updateBoard(id, { enginePinned: 'linux' });
|
||||
useSimulatorStore.getState().startBoard(id);
|
||||
expect(opened.length).toBe(1);
|
||||
|
||||
// Stop closes the socket; a CLOSING socket used to look "alive" and
|
||||
// the next start became a no-op.
|
||||
useSimulatorStore.getState().stopBoard(id);
|
||||
bridge.connect();
|
||||
expect(opened.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
|
@ -1912,6 +1912,7 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
|||
},
|
||||
|
||||
startBoard: (boardId: string) => {
|
||||
const boardKindOfBoard = (b: { boardKind: string }): string => b.boardKind;
|
||||
const board = get().boards.find((b) => b.id === boardId);
|
||||
if (!board) return;
|
||||
|
||||
|
|
@ -1954,7 +1955,22 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
|||
});
|
||||
});
|
||||
} else {
|
||||
getBoardBridge(boardId)?.connect();
|
||||
const bridge = getBoardBridge(boardId);
|
||||
if (bridge) {
|
||||
bridge.connect();
|
||||
} else {
|
||||
// A Pi with no bridge cannot boot, and the optional-chain
|
||||
// version of this call failed in silence: no guest, no error,
|
||||
// and a toolbar still showing Stop. Say so.
|
||||
console.warn(
|
||||
`[${boardKindOfBoard(board)}] no bridge for ${boardId}: the guest cannot start`,
|
||||
);
|
||||
set((s) => ({
|
||||
boards: s.boards.map((b) =>
|
||||
b.id === boardId ? { ...b, running: false } : b,
|
||||
),
|
||||
}));
|
||||
}
|
||||
}
|
||||
} else if (isEsp32Kind(board.boardKind)) {
|
||||
// Pre-register sensors connected to this board so the QEMU worker
|
||||
|
|
|
|||
Loading…
Reference in New Issue