diff --git a/tests/e2e/httpd.spec.ts b/tests/e2e/httpd.spec.ts index 9e92b1af4..730506614 100644 --- a/tests/e2e/httpd.spec.ts +++ b/tests/e2e/httpd.spec.ts @@ -1,7 +1,7 @@ import { expect, test } from "@tests/support/fixtures.js"; test("rad web command reacts to port change", async ({ page, peerManager }) => { - const peer = await peerManager.startPeer({ + const peer = await peerManager.createPeer({ name: "port-test", }); await peer.startHttpd(8090); diff --git a/tests/support/fixtures.ts b/tests/support/fixtures.ts index efb1b257f..c55dd5a4a 100644 --- a/tests/support/fixtures.ts +++ b/tests/support/fixtures.ts @@ -156,6 +156,7 @@ export const test = base.extend<{ outputLog, }); await use(peerManager); + await peerManager.shutdown(); }, authenticatedPeer: async ({ page, peerManager }, use) => { @@ -190,9 +191,6 @@ export const test = base.extend<{ } await use(peer); - - await peer.stopHttpd(); - await peer.stopNode(); }, // eslint-disable-next-line no-empty-pattern diff --git a/tests/support/globalSetup.ts b/tests/support/globalSetup.ts index 387146c4b..63ba47c37 100644 --- a/tests/support/globalSetup.ts +++ b/tests/support/globalSetup.ts @@ -77,7 +77,7 @@ export default async function globalSetup(): Promise<() => void> { } return async () => { - await palm.stopNode(); + await peerManager.shutdown(); killAllProcesses(); }; } diff --git a/tests/support/peerManager.ts b/tests/support/peerManager.ts index 67de3c7bd..66f7bf24f 100644 --- a/tests/support/peerManager.ts +++ b/tests/support/peerManager.ts @@ -76,6 +76,10 @@ export interface PeerManager { name: string; gitOptions?: Record; }): Promise; + /** + * Kill all processes spawned by any of the peers + */ + shutdown(): Promise; } export async function createPeerManager(createParams: { @@ -110,6 +114,9 @@ export async function createPeerManager(createParams: { return peer; }, + async shutdown() { + await Promise.all(peers.map(peer => peer.shutdown())); + }, }; } @@ -165,6 +172,7 @@ export class RadiclePeer { #httpdProcess?: ExecaChildProcess; // Name for easy identification. Used on file system and in logs. #name: string; + #childProcesses: ExecaChildProcess[] = []; private constructor(props: { checkoutPath: string; @@ -270,9 +278,9 @@ export class RadiclePeer { public async stopHttpd() { if (!this.#httpdBaseUrl || !this.#httpdProcess) { - throw new Error("No httpd service running"); + return; } - this.#httpdProcess?.kill("SIGTERM"); + this.#httpdProcess.kill("SIGTERM"); await waitOn({ resources: [ @@ -358,6 +366,17 @@ export class RadiclePeer { }); } + /** + * Kill all child processes created with `spawn()`, the node process and the + * HTTP API process. + */ + public async shutdown() { + // We don’t care about proper cleanup. We just want to make sure that no + // processes are running anymore. + this.#childProcesses.forEach(p => p.kill("SIGKILL")); + await Promise.all([this.stopNode(), this.stopHttpd()]); + } + public get address(): string { if (!this.#listenSocketAddr) { throw new Error("Remote node has no listen addr yet"); @@ -413,6 +432,7 @@ export class RadiclePeer { }, }; const childProcess = Process.spawn(cmd, args, opts); + this.#childProcesses.push(childProcess); if (opts.logPrefix !== null) { void Process.prefixOutput(