chore: dev-stack switches, shared e2e harness, and desktop shell rules
- `LIVE_RELOAD=false npm run dev` keeps the renderer alive across a machine suspend; the reload client otherwise destroys the session under test. - `dev-peer.sh` plus a separate userdata dir runs a second local peer. - `tools/voice-probe.js` samples peer state and RTP counters from a live window, persisting to localStorage so a renderer reload cannot erase it. - e2e helpers for voice pairs, peer-role election, and a TURN relay. - Electron single-instance and dev-client-load decisions move into rules files with colocated specs.
This commit is contained in:
+85
-18
@@ -1,11 +1,18 @@
|
||||
import { spawn, type ChildProcess } from 'node:child_process';
|
||||
import { once } from 'node:events';
|
||||
import { mkdtemp, rm } from 'node:fs/promises';
|
||||
import { createServer } from 'node:net';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
|
||||
export interface TestServerHandle {
|
||||
port: number;
|
||||
url: string;
|
||||
restart: () => Promise<void>;
|
||||
/** Kill the process but keep the port and data dir, so `start()` can bring it back. */
|
||||
kill: () => Promise<void>;
|
||||
/** Start the server again on the same port and data dir after `kill()`. */
|
||||
start: () => Promise<void>;
|
||||
stop: () => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -15,27 +22,15 @@ const START_SERVER_SCRIPT = join(E2E_DIR, 'helpers', 'start-test-server.js');
|
||||
export async function startTestServer(retries = 3): Promise<TestServerHandle> {
|
||||
for (let attempt = 1; attempt <= retries; attempt++) {
|
||||
const port = await allocatePort();
|
||||
const child = spawn(process.execPath, [START_SERVER_SCRIPT], {
|
||||
cwd: E2E_DIR,
|
||||
env: {
|
||||
...process.env,
|
||||
TEST_SERVER_PORT: String(port)
|
||||
},
|
||||
stdio: 'pipe'
|
||||
});
|
||||
const dataDir = await mkdtemp(join(tmpdir(), 'metoyou-e2e-handle-'));
|
||||
|
||||
child.stdout?.on('data', (chunk: Buffer | string) => {
|
||||
process.stdout.write(chunk.toString());
|
||||
});
|
||||
|
||||
child.stderr?.on('data', (chunk: Buffer | string) => {
|
||||
process.stderr.write(chunk.toString());
|
||||
});
|
||||
let child: ChildProcess | null = null;
|
||||
let stopped = false;
|
||||
|
||||
try {
|
||||
await waitForServerReady(port, child);
|
||||
child = await spawnTestServer(port, dataDir);
|
||||
} catch (error) {
|
||||
await stopServer(child);
|
||||
await rm(dataDir, { recursive: true, force: true });
|
||||
|
||||
if (attempt < retries) {
|
||||
console.log(`[E2E Server] Attempt ${attempt} failed, retrying...`);
|
||||
@@ -48,8 +43,51 @@ export async function startTestServer(retries = 3): Promise<TestServerHandle> {
|
||||
return {
|
||||
port,
|
||||
url: `http://localhost:${port}`,
|
||||
restart: async () => {
|
||||
if (stopped) {
|
||||
throw new Error('Cannot restart a stopped test server');
|
||||
}
|
||||
|
||||
if (child) {
|
||||
await stopServer(child);
|
||||
}
|
||||
|
||||
child = await spawnTestServer(port, dataDir);
|
||||
},
|
||||
kill: async () => {
|
||||
if (stopped) {
|
||||
throw new Error('Cannot kill a stopped test server');
|
||||
}
|
||||
|
||||
if (child) {
|
||||
await stopServer(child);
|
||||
child = null;
|
||||
}
|
||||
},
|
||||
start: async () => {
|
||||
if (stopped) {
|
||||
throw new Error('Cannot start a stopped test server');
|
||||
}
|
||||
|
||||
if (child) {
|
||||
return;
|
||||
}
|
||||
|
||||
child = await spawnTestServer(port, dataDir);
|
||||
},
|
||||
stop: async () => {
|
||||
await stopServer(child);
|
||||
if (stopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
stopped = true;
|
||||
|
||||
if (child) {
|
||||
await stopServer(child);
|
||||
child = null;
|
||||
}
|
||||
|
||||
await rm(dataDir, { recursive: true, force: true });
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -57,6 +95,35 @@ export async function startTestServer(retries = 3): Promise<TestServerHandle> {
|
||||
throw new Error('startTestServer: unreachable');
|
||||
}
|
||||
|
||||
async function spawnTestServer(port: number, dataDir: string): Promise<ChildProcess> {
|
||||
const child = spawn(process.execPath, [START_SERVER_SCRIPT], {
|
||||
cwd: E2E_DIR,
|
||||
env: {
|
||||
...process.env,
|
||||
TEST_SERVER_DATA_DIR: dataDir,
|
||||
TEST_SERVER_PORT: String(port)
|
||||
},
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
child.stdout?.on('data', (chunk: Buffer | string) => {
|
||||
process.stdout.write(chunk.toString());
|
||||
});
|
||||
|
||||
child.stderr?.on('data', (chunk: Buffer | string) => {
|
||||
process.stderr.write(chunk.toString());
|
||||
});
|
||||
|
||||
try {
|
||||
await waitForServerReady(port, child);
|
||||
} catch (error) {
|
||||
await stopServer(child);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
async function allocatePort(): Promise<number> {
|
||||
return await new Promise<number>((resolve, reject) => {
|
||||
const probe = createServer();
|
||||
|
||||
Reference in New Issue
Block a user