fix(ci): fix cross-platform test spawning, redis disconnect handling, and ci step guards

- Fix worker spawning in tests/chaos/crash.test.ts to support Linux runners

- Gracefully handle closed connections during RedisClient.disconnect()

- Detach SIGINT/SIGTERM process listeners on NotifkitServer.stop()

- Only run coverage badge generation if the test step was not skipped
This commit is contained in:
coo1estguy
2026-09-01 14:17:38 +05:30
parent c495a84464
commit 8438cdb3a2
4 changed files with 45 additions and 15 deletions
+3 -1
View File
@@ -50,12 +50,14 @@ jobs:
# though they cannot locally. This is the only place the real coverage
# number exists.
- name: Test
id: test
run: npm test
- name: Generate coverage badge
# Runs even when the suite fails its thresholds, so the badge reports
# what coverage actually is rather than going stale on a red build.
if: always()
# Only runs if the test step was actually executed (not skipped due to earlier failure).
if: always() && steps.test.outcome != 'skipped'
id: badge
run: node scripts/coverage-badge.mjs --out badge/coverage.json
+12 -1
View File
@@ -58,9 +58,20 @@ export class RedisClient {
}
async disconnect(): Promise<void> {
if (this.isClosing) return;
this.isClosing = true;
this.logger?.info("disconnecting redis");
await this.native.quit();
try {
if (this.native.status !== "end" && this.native.status !== "close") {
await this.native.quit();
}
} catch {
try {
this.native.disconnect();
} catch {
// ignore errors on forced disconnect
}
}
this.logger?.info("redis disconnected");
}
}
+14 -4
View File
@@ -31,6 +31,8 @@ export class NotifkitServer extends EventEmitter {
private redisContainer: any = null;
private eventCleanupFns: (() => void)[] = [];
private signalHandlersAttached = false;
private sigintHandler?: () => void;
private sigtermHandler?: () => void;
private logger: Logger;
constructor(options: NotifkitOptions) {
@@ -192,12 +194,14 @@ export class NotifkitServer extends EventEmitter {
};
if (!this.signalHandlersAttached) {
process.once("SIGINT", () => {
this.sigintHandler = () => {
void handleSignal("SIGINT");
});
process.once("SIGTERM", () => {
};
this.sigtermHandler = () => {
void handleSignal("SIGTERM");
});
};
process.once("SIGINT", this.sigintHandler);
process.once("SIGTERM", this.sigtermHandler);
this.signalHandlersAttached = true;
}
@@ -205,6 +209,12 @@ export class NotifkitServer extends EventEmitter {
}
async stop() {
if (this.signalHandlersAttached) {
if (this.sigintHandler) process.removeListener("SIGINT", this.sigintHandler);
if (this.sigtermHandler) process.removeListener("SIGTERM", this.sigtermHandler);
this.signalHandlersAttached = false;
}
// Cleanup event listeners to prevent memory leaks
for (const cleanup of this.eventCleanupFns) {
cleanup();
+16 -9
View File
@@ -26,16 +26,23 @@ describe("Crash Testing (Chaos Monkey)", () => {
const spawnWorker = (type: string, id: string) => {
logger.info(`Spawning worker: ${type} (${id})`);
const cp = spawn("cmd", ["/c", "npx", "--no-install", "tsx", "tests/chaos/worker-runner.ts"], {
env: {
...process.env,
WORKER_TYPE: type,
DATABASE_URL: dbUrl,
REDIS_URL: redisUrl,
LOG_LEVEL: "silent",
const isWin = process.platform === "win32";
const cp = spawn(
isWin ? "cmd.exe" : "npx",
isWin
? ["/c", "npx", "--no-install", "tsx", "tests/chaos/worker-runner.ts"]
: ["--no-install", "tsx", "tests/chaos/worker-runner.ts"],
{
env: {
...process.env,
WORKER_TYPE: type,
DATABASE_URL: dbUrl,
REDIS_URL: redisUrl,
LOG_LEVEL: "silent",
},
stdio: "ignore", // ignore output to keep logs clean
},
stdio: "ignore", // ignore output to keep logs clean
});
);
cp.on("exit", (code, signal) => {
logger.info(`Worker ${type} (${id}) exited with signal ${signal} (code ${code})`);