From 5400311be160ef781aed44fa7500a0fdb5a439ce Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 8 Aug 2026 16:48:07 -0700 Subject: [PATCH] test(process): make Linux adapter wiring portable (#120741) Co-authored-by: Peter Steinberger --- src/process/supervisor/adapters/child.test.ts | 3 +++ src/process/supervisor/adapters/pty.test.ts | 9 +++++++-- src/process/supervisor/adapters/test-support.ts | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/process/supervisor/adapters/child.test.ts b/src/process/supervisor/adapters/child.test.ts index b7cdea00f0ae..2bd099634d4c 100644 --- a/src/process/supervisor/adapters/child.test.ts +++ b/src/process/supervisor/adapters/child.test.ts @@ -10,6 +10,7 @@ import { useAutoCleanupTempDirTracker } from "../../../../test/helpers/temp-dir. import { expectRealExitWinsOverSigkillFallback, expectWaitStaysPendingUntilSigkillFallback, + mockLinuxOomWrapperShell, } from "./test-support.js"; const { spawnWithFallbackMock, signalProcessTreeMock, createWindowsOutputDecoderMock } = vi.hoisted( @@ -742,6 +743,7 @@ describe("createChildAdapter", () => { const originalEnv = process.env.ENV; const originalCdpath = process.env.CDPATH; setPlatform("linux"); + const restoreLinuxShell = mockLinuxOomWrapperShell(); process.env.BASH_ENV = "/tmp/bashenv"; process.env.ENV = "/tmp/env"; process.env.CDPATH = "/tmp"; @@ -752,6 +754,7 @@ describe("createChildAdapter", () => { }); expect(adapter.oomScoreWrapperSelected).toBe(true); } finally { + restoreLinuxShell(); if (originalBashEnv === undefined) { delete process.env.BASH_ENV; } else { diff --git a/src/process/supervisor/adapters/pty.test.ts b/src/process/supervisor/adapters/pty.test.ts index ec16bc7898cf..ba03d580502d 100644 --- a/src/process/supervisor/adapters/pty.test.ts +++ b/src/process/supervisor/adapters/pty.test.ts @@ -3,6 +3,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import { expectRealExitWinsOverSigkillFallback, expectWaitStaysPendingUntilSigkillFallback, + mockLinuxOomWrapperShell, } from "./test-support.js"; const { spawnMock, ptyKillMock, signalProcessTreeMock } = vi.hoisted(() => ({ @@ -157,7 +158,7 @@ describe("createPtyAdapter", () => { it("forwards non-SIGTERM explicit signals to node-pty kill on non-Windows", async () => { const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform"); - Object.defineProperty(process, "platform", { value: "linux", configurable: true }); + Object.defineProperty(process, "platform", { value: "darwin", configurable: true }); try { spawnMock.mockReturnValue(createStubPty()); @@ -316,17 +317,21 @@ describe("createPtyAdapter", () => { it("wraps Linux PTY spawns so shell children inherit higher OOM score", async () => { const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform"); Object.defineProperty(process, "platform", { value: "linux", configurable: true }); + const restoreLinuxShell = mockLinuxOomWrapperShell(); + vi.resetModules(); try { + const { createPtyAdapter: createLinuxPtyAdapter } = await import("./pty.js"); const stub = createStubPty(); spawnMock.mockReturnValue(stub); - const adapter = await createPtyAdapter({ + const adapter = await createLinuxPtyAdapter({ shell: "bash", args: ["-lc", "env"], env: { PATH: "/usr/bin", BASH_ENV: "/tmp/bashenv", TERM: "dumb" }, }); expect(adapter.oomScoreWrapperSelected).toBe(true); } finally { + restoreLinuxShell(); if (originalPlatform) { Object.defineProperty(process, "platform", originalPlatform); } diff --git a/src/process/supervisor/adapters/test-support.ts b/src/process/supervisor/adapters/test-support.ts index 06dd19bf7edd..f82aa4f9d41c 100644 --- a/src/process/supervisor/adapters/test-support.ts +++ b/src/process/supervisor/adapters/test-support.ts @@ -1,4 +1,5 @@ // Supervisor adapter test support builds mock process handles for adapter tests. +import fs from "node:fs"; import { expect, vi } from "vitest"; /** @@ -11,6 +12,22 @@ type WaitResult = { signal: number | NodeJS.Signals | null; }; +/** Keep Linux adapter wiring tests deterministic on hosts without `/bin/sh`. */ +export function mockLinuxOomWrapperShell(): () => void { + const statSync = fs.statSync.bind(fs); + const fileStats = statSync(process.execPath); + const statSyncMock = vi + .spyOn(fs, "statSync") + .mockImplementation((target, options) => + String(target) === "/bin/sh" ? fileStats : statSync(target, options as never), + ); + return () => { + statSyncMock.mockRestore(); + // The production helper memoizes shell availability; clear it for later tests. + vi.resetModules(); + }; +} + /** Assert fallback SIGKILL resolves only after the grace timer expires. */ export async function expectWaitStaysPendingUntilSigkillFallback( waitPromise: Promise,