Skip to content

Commit

Permalink
fix(tests): fixed createInstance's error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
DuCanhGH committed Jan 11, 2024
1 parent 9ea8ac0 commit eeefa28
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 11 deletions.
6 changes: 3 additions & 3 deletions packages/next-pwa/__tests__/test-utils/create-describe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ const createNext = async (opts: NextTestOpts) => {
await nextInstance.spawn();
return nextInstance;
} catch (err) {
console.error(`failed to create next instance: ${err}, cliOutput: ${nextInstance?.cliOutput ?? "N/A"}`);
console.error(`failed to create next instance: ${err}, cliOutput:${nextInstance?.cliOutput ? `\n${nextInstance.cliOutput}` : "N/A"}`);
try {
void nextInstance?.destroy();
await nextInstance?.destroy();
} catch (err) {
console.error("failed to clean up after failure", err);
}
Expand All @@ -52,7 +52,7 @@ export const createDescribe = (name: string, opts: NextTestOpts, fn: (args: { ne
next = await createNext(opts);
});
afterAll(async () => {
await next.destroy();
await next?.destroy();
});
const nextProxy = new Proxy<NextInstance>({} as NextInstance, {
get(_target, property: keyof NextInstance) {
Expand Down
35 changes: 27 additions & 8 deletions packages/next-pwa/__tests__/test-utils/next-instance-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import * as cheerio from "cheerio";
import fsExtra from "fs-extra";
import type { PackageJson } from "type-fest";

import treeKill from "./tree-kill.ts";

export interface NextInstanceOpts {
skipInstall: boolean;
dependencies?: PackageJson["dependencies"];
Expand Down Expand Up @@ -161,14 +163,31 @@ export abstract class NextInstance {
throw new Error("next instance already destroyed");
}
this._isDestroyed = true;
let exitResolve: () => void;
const exitPromise = new Promise<void>((resolve) => {
exitResolve = resolve;
});
this._process?.addListener("exit", () => exitResolve());
this._process?.kill();
await exitPromise;
this._process = undefined;
if (this._process) {
try {
let exitResolve: () => void;
const exitPromise = new Promise<void>((resolve) => {
exitResolve = resolve;
});
this._process.addListener("exit", () => exitResolve());
await new Promise<void>((resolve) => {
if (this._process?.pid) {
treeKill(this._process.pid, "SIGKILL", (err) => {
if (err) {
console.error("Failed to kill tree of process", this._process?.pid, "err:", err);
}
resolve();
});
}
});
this._process.kill("SIGKILL");
await exitPromise;
this._process = undefined;
console.log("Stopped next server");
} catch (err) {
console.error("Failed to stop next server", err);
}
}
await this.clean();
}
public async fetch(pathname: string, init?: RequestInit) {
Expand Down
116 changes: 116 additions & 0 deletions packages/next-pwa/__tests__/test-utils/tree-kill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { ChildProcessWithoutNullStreams, exec, spawn } from "node:child_process";

type Callback = (error?: Error) => void;

export default (pid: number, signal?: string | number, callback?: Callback) => {
if (typeof signal === "function" && callback === undefined) {
callback = signal;
signal = undefined;
}

if (Number.isNaN(pid)) {
if (callback) {
return callback(new Error("pid must be a number"));
}
throw new Error("pid must be a number");
}

const tree = new Map<number, number[]>([[pid, []]]);
const pidsToProcess = new Set<number>([pid]);

switch (process.platform) {
case "win32":
exec(`taskkill /pid ${pid} /T /F`, (err) => void (!!err && callback?.(err)));
break;
case "darwin":
buildProcessTree(
pid,
tree,
pidsToProcess,
(parentPid) => spawn("pgrep", ["-P", `${parentPid}`]),
() => void killAll(tree, signal, callback),
);
break;
default: // Linux
buildProcessTree(
pid,
tree,
pidsToProcess,
(parentPid) => spawn("ps", ["-o", "pid", "--no-headers", "--ppid", `${parentPid}`]),
() => void killAll(tree, signal, callback),
);
break;
}
};

function killAll(tree: Map<number, number[]>, signal: string | number | undefined, callback: Callback | undefined) {
const killed: Record<number, boolean> = {};
try {
tree.forEach((tree_pid) => {
for (const pidpid of tree_pid) {
if (!killed[pidpid]) {
killPid(pidpid, signal);
killed[pidpid] = true;
}
}
});
} catch (err) {
if (callback) {
return callback(err as Error | undefined);
}
throw err;
}
if (callback) {
return callback();
}
}

function killPid(pid: number, signal: string | number | undefined) {
try {
process.kill(pid, signal);
} catch (err) {
if ((err as any).code !== "ESRCH") {
throw err;
}
}
}

function buildProcessTree(
parentPid: number,
tree: Map<number, number[]>,
pidsToProcess: Set<number>,
spawnChildProcessesList: (parentPid: number) => ChildProcessWithoutNullStreams,
cb: Callback | undefined,
) {
const ps = spawnChildProcessesList(parentPid);
let allData = "";
ps.stdout.on("data", (data: Buffer) => {
allData += data.toString("ascii");
});

const onClose = (code: number | null) => {
pidsToProcess.delete(parentPid);

if (code !== null && code !== 0) {
// no more parent processes
if (pidsToProcess.size === 0) {
cb?.(undefined);
}
return;
}

const matches = allData.match(/\d+/g);

if (matches) {
for (const pidMatch of matches) {
const pid = parseInt(pidMatch, 10);
tree.get(parentPid)?.push(pid);
tree.set(pid, []);
pidsToProcess.add(pid);
buildProcessTree(pid, tree, pidsToProcess, spawnChildProcessesList, cb);
}
}
};

ps.on("close", onClose);
}

0 comments on commit eeefa28

Please sign in to comment.