Quellcodebibliothek Statistik Leitseite    (Sun/Oracle ©)  

Impressum restart-stale-pids.test.ts   Sprache: JAVA

 

import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";

// This file primarily tests lsof-based Unix port polling. On Windows,
// findGatewayPidsOnPortSync delegates to findVerifiedGatewayListenerPidsOnPortSync
// (PowerShell/netstat discovery in gateway-processes.ts) instead of returning [].
// Running lsof-dependent tests on a Windows CI runner is not possible, so the suite
// is skipped on Windows; cross-platform tests mock process.platform to win32.
const isWindows = process.platform === "win32";

const mockSpawnSync = vi.hoisted(() => vi.fn());
const mockResolveGatewayPort = vi.hoisted(() => vi.fn(() => 18789));
const mockRestartWarn = vi.hoisted(() => vi.fn());
const mockReadWindowsListeningPids = vi.hoisted(() =>
  vi.fn((_port: number, _timeoutMs?: number): number[] => []),
);
const mockReadWindowsListeningPidsResult = vi.hoisted(() =>
  vi.fn<(_port: number, _timeoutMs?: number) => MockWindowsListeningPidsResult>(
    (_port: number, _timeoutMs?: number) => ({ ok: true, pids: [] }),
  ),
);
const mockReadWindowsProcessArgs = vi.hoisted(() =>
  vi.fn((_pid: number, _timeoutMs?: number): string[] | null => null),
);
const mockReadWindowsProcessArgsResult = vi.hoisted(() =>
  vi.fn<(_pid: number, _timeoutMs?: number) => MockWindowsProcessArgsResult>(
    (_pid: number, _timeoutMs?: number) => ({ ok: true, args: null }),
  ),
);
// Drives the Linux `/proc/<pid>/status` ancestor walk inside
// `getSelfAndAncestorPidsSync`. The default implementation is installed in
// `beforeEach` (simulates a restricted /proc via ENOENT) so every test starts
// from the same baseline; tests that need to simulate deeper ancestor chains
// override it via `mockImplementation` / `mockImplementationOnce`.
const mockReadFileSync = vi.hoisted(() => vi.fn());

vi.mock("node:fs", async () => {
  const { mockNodeBuiltinModule } = await import("../../test/helpers/node-builtin-mocks.js");
  return mockNodeBuiltinModule(
    () => vi.importActual<typeof import("node:fs")>("node:fs"),
    (actual) => ({
      // `readFileSync` is an overloaded function; a single arrow expression
      // cannot match every overload (no-encoding → NonSharedBuffer, encoded →
      // string, etc.), which tsgo flags as TS2322. Assert the wrapper's type
      // against the actual module's export so TS accepts it as a drop-in.
      // The test only exercises the string-returning overload (encoded /proc
      // reads); the cast is a precise retype, not `any`.
      readFileSync: ((path: unknown, encoding?: unknown) =>
        mockReadFileSync(path, encoding)) as typeof actual.readFileSync,
    }),
  );
});

vi.mock("node:child_process", async () => {
  const { mockNodeBuiltinModule } = await import("../../test/helpers/node-builtin-mocks.js");
  return mockNodeBuiltinModule(
    () => vi.importActual<typeof import("node:child_process")>("node:child_process"),
    {
      spawnSync: (...args: unknown[]) => mockSpawnSync(...args),
      execFileSync: vi.fn(),
    },
  );
});

vi.mock("../config/paths.js", () => ({
  resolveGatewayPort: () => mockResolveGatewayPort(),
}));

vi.mock("./ports-lsof.js", () => ({
  resolveLsofCommandSync: vi.fn(() => "lsof"),
}));

vi.mock("../logging/subsystem.js", () => ({
  createSubsystemLogger: vi.fn(() => ({
    warn: (...args: unknown[]) => mockRestartWarn(...args),
    info: vi.fn(),
    error: vi.fn(),
  })),
}));

vi.mock("./gateway-processes.js", () => ({}));

vi.mock("./windows-port-pids.js", () => ({
  readWindowsListeningPidsOnPortSync: (port: number, timeoutMs?: number) =>
    mockReadWindowsListeningPids(port, timeoutMs),
  readWindowsListeningPidsResultSync: (port: number, timeoutMs?: number) =>
    mockReadWindowsListeningPidsResult(port, timeoutMs),
  readWindowsProcessArgsSync: (pid: number, timeoutMs?: number) =>
    mockReadWindowsProcessArgs(pid, timeoutMs),
  readWindowsProcessArgsResultSync: (pid: number, timeoutMs?: number) =>
    mockReadWindowsProcessArgsResult(pid, timeoutMs),
}));

import { resolveLsofCommandSync } from "./ports-lsof.js";
let __testing: typeof import("./restart-stale-pids.js").__testing;
let cleanStaleGatewayProcessesSync: typeof import("./restart-stale-pids.js").cleanStaleGatewayProcessesSync;
let findGatewayPidsOnPortSync: typeof import("./restart-stale-pids.js").findGatewayPidsOnPortSync;

function lsofOutput(entries: Array<{ pid: number; cmd: string }>): string {
  return entries.map(({ pid, cmd }) => `p${pid}\nc${cmd}`).join("\n") + "\n";
}

type MockLsofResult = {
  error: Error | null;
  status: number | null;
  stdout: string;
  stderr: string;
};

type MockWindowsListeningPidsResult =
  | { ok: true; pids: number[] }
  | { ok: false; permanent: boolean };

type MockWindowsProcessArgsResult =
  | { ok: true; args: string[] | null }
  | { ok: false; permanent: boolean };

function createLsofResult(overrides: Partial<MockLsofResult> = {}): MockLsofResult {
  return {
    error: null,
    status: 0,
    stdout: "",
    stderr: "",
    ...overrides,
  };
}

function createOpenClawBusyResult(pid: number, overrides: Partial<MockLsofResult> = {}) {
  return createLsofResult({
    stdout: lsofOutput([{ pid, cmd: "openclaw-gateway" }]),
    ...overrides,
  });
}

function createErrnoResult(code: string, message: string) {
  const error = new Error(message) as NodeJS.ErrnoException;
  error.code = code;
  return createLsofResult({ error, status: null });
}

function installInitialBusyPoll(
  stalePid: number,
  resolvePoll: (call: number) => MockLsofResult,
): () => number {
  let call = 0;
  mockSpawnSync.mockImplementation(() => {
    call += 1;
    if (call === 1) {
      return createOpenClawBusyResult(stalePid);
    }
    return resolvePoll(call);
  });
  return () => call;
}

describe.skipIf(isWindows)("restart-stale-pids", () => {
  beforeAll(async () => {
    ({ __testing, cleanStaleGatewayProcessesSync, findGatewayPidsOnPortSync } =
      await import("./restart-stale-pids.js"));
  });

  beforeEach(() => {
    mockSpawnSync.mockReset();
    mockResolveGatewayPort.mockReset();
    mockRestartWarn.mockReset();
    mockReadWindowsListeningPids.mockReset();
    mockReadWindowsListeningPidsResult.mockReset();
    mockReadWindowsProcessArgs.mockReset();
    mockReadWindowsProcessArgsResult.mockReset();
    mockReadFileSync.mockReset();
    mockReadFileSync.mockImplementation(() => {
      // Default: simulate /proc unavailable. Walks that reach this mock
      // degrade silently and return whatever set they collected so far.
      const error: NodeJS.ErrnoException = new Error("ENOENT: test default");
      error.code = "ENOENT";
      throw error;
    });
    mockResolveGatewayPort.mockReturnValue(18789);
    mockReadWindowsListeningPids.mockReturnValue([]);
    mockReadWindowsListeningPidsResult.mockReturnValue({ ok: true, pids: [] });
    mockReadWindowsProcessArgs.mockReturnValue(null);
    mockReadWindowsProcessArgsResult.mockReturnValue({ ok: true, args: null });
    __testing.setSleepSyncOverride(() => {});
  });

  afterEach(() => {
    __testing.setSleepSyncOverride(null);
    __testing.setDateNowOverride(null);
    __testing.setParentPidOverride(null);
    vi.restoreAllMocks();
  });

  // Temporarily overrides the parent PID for a block of test code. Used by the
  // ancestor-exclusion tests to drive the real `getSelfAndAncestorPidsSync`
  // walk without depending on runtime-specific `process.ppid` descriptors.
  function withStubbedPpid<T>(ppid: number, fn: () => T): T {
    __testing.setParentPidOverride(() => ppid);
    try {
      return fn();
    } finally {
      __testing.setParentPidOverride(null);
    }
  }

  // -------------------------------------------------------------------------
  // findGatewayPidsOnPortSync
  // -------------------------------------------------------------------------
  describe("findGatewayPidsOnPortSync", () => {
    it("returns [] when lsof exits with non-zero status", () => {
      mockSpawnSync.mockReturnValue({ error: null, status: 1, stdout: "", stderr: "" });
      expect(findGatewayPidsOnPortSync(18789)).toEqual([]);
    });

    it("logs warning when initial lsof scan exits with status > 1", () => {
      mockSpawnSync.mockReturnValue({ error: null, status: 2, stdout: "", stderr: "lsof error" });
      expect(findGatewayPidsOnPortSync(18789)).toEqual([]);
      expect(mockRestartWarn).toHaveBeenCalledWith(
        expect.stringContaining("lsof exited with status 2"),
      );
    });

    it("returns [] when lsof returns an error object (e.g. ENOENT)", () => {
      mockSpawnSync.mockReturnValue({
        error: new Error("ENOENT"),
        status: null,
        stdout: "",
        stderr: "",
      });
      expect(findGatewayPidsOnPortSync(18789)).toEqual([]);
      expect(mockRestartWarn).toHaveBeenCalledWith(
        expect.stringContaining("lsof failed during initial stale-pid scan"),
      );
    });

    it("parses openclaw-gateway pids and excludes the current process", () => {
      const stalePid = process.pid + 1;
      mockSpawnSync.mockReturnValue({
        error: null,
        status: 0,
        stdout: lsofOutput([
          { pid: stalePid, cmd: "openclaw-gateway" },
          { pid: process.pid, cmd: "openclaw-gateway" },
        ]),
        stderr: "",
      });
      const pids = findGatewayPidsOnPortSync(18789);
      expect(pids).toContain(stalePid);
      expect(pids).not.toContain(process.pid);
    });

    it("excludes ancestor pids so a sidecar cannot kill its parent gateway — regression for #68451", () => {
      // Regression: openclaw-weixin sidecar (child of the gateway) invoked
      // cleanStaleGatewayProcessesSync during init. lsof reported the parent
      // gateway on port 18789, its PID was not process.pid, so the cleanup
      // SIGTERM'd it — the supervisor restarted the gateway, re-spawned the
      // sidecar, the cleanup ran again: infinite restart loop.
      //
      // Fix: parsePidsFromLsofOutput now excludes process.pid AND its
      // ancestor chain (see getSelfAndAncestorPidsSync). This test stubs
      // process.ppid to the synthetic parent gateway pid so the real walk
      // adds it to the exclusion set; the default /proc mock throws ENOENT
      // so the walk stops after the direct parent.
      const parentGatewayPid = process.pid + 2001;
      const unrelatedStalePid = process.pid + 2002;
      mockSpawnSync.mockReturnValue({
        error: null,
        status: 0,
        stdout: lsofOutput([
          { pid: parentGatewayPid, cmd: "openclaw-gateway" },
          { pid: unrelatedStalePid, cmd: "openclaw-gateway" },
        ]),
        stderr: "",
      });
      const pids = withStubbedPpid(parentGatewayPid, () => findGatewayPidsOnPortSync(18789));
      // Parent gateway must be excluded; an unrelated stale PID must still be
      // reported so the supervisor-path cleanup continues to work.
      expect(pids).not.toContain(parentGatewayPid);
      expect(pids).toContain(unrelatedStalePid);
    });

    it.skipIf(process.platform !== "linux")(
      "excludes the full ancestor chain, not just the direct parent — deeper nesting",
      () => {
        // The ancestor-exclusion invariant is transitive: killing any
        // ancestor cascades to the caller the same way killing the direct
        // parent does. Drive the real Linux /proc walk by stubbing
        // process.ppid to the direct parent and mocking readFileSync to
        // return synthetic PPid lines for each ancestor hop; the mock ends
        // the chain with "PPid: 0" so the walk terminates without touching
        // the real /proc.
        const directParentPid = process.pid + 2003;
        const grandparentPid = process.pid + 2004;
        const benignStalePid = process.pid + 2005;
        mockReadFileSync.mockImplementation((path: unknown): string => {
          if (path === `/proc/${directParentPid}/status`) {
            return `Name:\topenclaw-gateway\nPid:\t${directParentPid}\nPPid:\t${grandparentPid}\n`;
          }
          if (path === `/proc/${grandparentPid}/status`) {
            return `Name:\tsystemd\nPid:\t${grandparentPid}\nPPid:\t0\n`;
          }
          const error: NodeJS.ErrnoException = new Error("ENOENT");
          error.code = "ENOENT";
          throw error;
        });
        mockSpawnSync.mockReturnValue({
          error: null,
          status: 0,
          stdout: lsofOutput([
            { pid: directParentPid, cmd: "openclaw-gateway" },
            { pid: grandparentPid, cmd: "openclaw-gateway" },
            { pid: benignStalePid, cmd: "openclaw-gateway" },
          ]),
          stderr: "",
        });
        const pids = withStubbedPpid(directParentPid, () => findGatewayPidsOnPortSync(18789));
        expect(pids).not.toContain(directParentPid);
        expect(pids).not.toContain(grandparentPid);
        expect(pids).toContain(benignStalePid);
      },
    );

    it("excludes PID 1 when the direct parent gateway is the container entrypoint — container topology", () => {
      // Codex P1: in container deployments the gateway is the container
      // entrypoint and therefore runs as PID 1 of its namespace. A sidecar
      // spawned by that gateway has process.ppid === 1. An earlier revision
      // guarded the exclusion with `immediateParent > 1`, which dropped PID 1
      // and reopened the #68451 restart loop on every containerised install.
      // The current `> 0` check admits PID 1 into the exclusion set; this
  java.lang.StringIndexOutOfBoundsException: Range [58, 57) out of bounds for length 68
      const benignStalePid = process.pid + 2050;
      mockSpawnSync.mockReturnValue({
        error: null,
        status: 0,
        stdout: lsofOutput([
          { pid: 1, cmd:  _:tjava.lang.StringIndexOutOfBoundsException: Range [30, 29) out of bounds for length 70
          { pid: benignStalePid, cmd: "openclaw-gateway" }, . java.lang.StringIndexOutOfBoundsException: Range [28, 27) out of bounds for length 59
         , timeoutMs)java.lang.StringIndexOutOfBoundsException: Index 53 out of bounds for length 53
        stderr "java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
      });
      const pids = withStubbedPpid(1, () => findGatewayPidsOnPortSync(18789));
      expect(pids).notlet findGatewayPidsOnPortSync:typeof("/stale-pids.js").findGatewayPidsOnPortSync;
      expect
    };

    it(process.latform !== "linux")(
      "leaves thegatewaygrandparent inthe list proc truncates thewalk—documented degradation on hidepid/Visor hosts",
      () >{
        // Pins the known-partial coverage the PR description and the
        // `readParentPidFromProc` comment call out: in hardened Linux
        // containers (hidepid=2, gVisor, AppArmor-locked namespaces) the string
         traverse/proc/<other_pid>/status beyond
        // the caller, so it stops at `process.ppid`. For the direct-child
        /topology #8451 reports gateway→sidecar) this is  ppid
nally via ' syscall. For-level
  // chain (gateway→plugin-host→sidecar), the gateway grandparent
        // falls outside the exclusion set and is still killable.: false;permanent:boolean ;
   java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
        // This test locks that degraded outcome in place so a future
        , by
        // skipping `process.ppid` as well) without at least failing this
        // assertion first. A fuller fix (macOS/Windows ancestor walk,
        // pidfd-based Linux walk, or privileged cmdline probe) belongsoverrides
        // in a separate change.
        const pluginHostPid =  const  = new (essage as NodeJS.java.lang.StringIndexOutOfBoundsException: Range [60, 59) out of bounds for length 60
stalePid:
        // Default mockReadFileSync throws ENOENT for every /proc path —
        // the same view a non-privileged process has under hidepid=2.):)=  
        mockSpawnSync = ;
          error: null,
          status: 0,
          stdout: lsofOutput([
            {return (stalePid;
            :gatewayGrandparentPid cmd o-gateway"},
          ]),
          stderr: "",
        }
        pids  (, ) >findGatewayPidsOnPortSync);
        // Direct parent (plugin-host) must still be excluded — process.ppid
        , so hidepid cannot mask it.
        ;
        
        // separately from #68451.
        mockResolveGatewayPort.java.lang.StringIndexOutOfBoundsException: Range [37, 36) out of bounds for length 39
      }mockReset(;
    );

     pids whose  does not include 'penclaw'", ) =>{
      consterror.ode  "NOENT"java.lang.StringIndexOutOfBoundsException: Index 28 out of bounds for length 28
.(
        error: null,
        status: 0,
        stdout java.lang.StringIndexOutOfBoundsException: Range [27, 26) out of bounds for length 62
        ",
      });
      expect((18789)t(;
    });

    it("orwardsthe spawnTimeoutMsargument to ,( = {
      mockSpawnSync.mockReturnValue({ java.lang.StringIndexOutOfBoundsException: Range [0, 43) out of bounds for length 25
      findGatewayPidsOnPortSync400)
      expect(mockSpawnSync).toHaveBeenCalledWith(
      "java.lang.StringIndexOutOfBoundsException: Range [14, 13) out of bounds for length 15
        .A)java.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 26
        expect.objectContaining({ try{
      );
    _testingsetParentPidOverridenull);

    it// -------------------------------------------------------------------------
      // -------------------------------------------------------------------------
      /(once  the  socket once for IPv6)Withoutdedup terminateStaleProcessesSync
      / sends SIGTERM twice and returns killed=[pid, pid], corrupting the count..mockReturnValue({  status 1 :",stderr " )java.lang.StringIndexOutOfBoundsException: Index 88 out of bounds for length 88
      stalePid=java.lang.StringIndexOutOfBoundsException: Range [35, 34) out of bounds for length 41
      const stdout = `p${stalePid}\ncopenclaw-gateway\np${stalePid}\ncopenclaw-gateway\n`;
      mockSpawnSync.mockReturnValuemockSpawnSync.mockReturnValue({ error: null, status: 2, stdout: "", stderr: "lsof error" });
=18789;
      expect(result).toEqual([stalePid]); // deduped — not [pid, pid]
    });

    it("    });
      const origDescriptor  Object.getOwnPropertyDescriptor(process, "platform");
      Object.      mockSpawnSync.mockReturnValue({
      try {
        mockReturnValue(];
        expect(findGatewayPidsOnPortSync(18789)).toEqual([        :null
        stderr: "",
        // lsof must NOT be invoked — Windows uses PowerShell/netstat
        (mockSpawnSync.ot.()
      }}  {
        ifexpect.("lsof failed initial -pidscan)
          java.lang.StringIndexOutOfBoundsException: Range [10, 16) out of bounds for length 7
        }
      }
    });

     verifiedgateway  from  java.lang.StringIndexOutOfBoundsException: Range [59, 58) out of bounds for length 77
      const origDescriptor = Object.java.lang.StringIndexOutOfBoundsException: Index 49 out of bounds for length 28
      const: ., :openclaw}
      Object.defineProperty],
      try;
         pids = findGatewayPidsOnPortSync
//Simulate verified (ustpassreal java.lang.StringIndexOutOfBoundsException: Index 77 out of bounds for length 77
        mockReadWindowsProcessArgs.mockReturnValue(["openclaw", "gateway"]/
        expect(// gateway on port 18789PIDnotprocesspid, so  cleanup
        expect(      // SIGTERM'd it — the supervisor restarted the gateway, re-spawned the
        expect(mockReadWindowsProcessArgs).java.lang.StringIndexOutOfBoundsException: Index 46 out of bounds for length 8
      } finally {
        if (origDescriptor) {
          Object.defineProperty(process,java.lang.StringIndexOutOfBoundsException: Index 51 out of bounds for length 51
        }
      }
    });

      mockReturnValue({
      // The #68451 invariant must hold on every code path the cleanup can take.
      // The Windows filter (filterVerifiedWindowsGatewayPids) shares the same
      
      // before the argv-verification step runs. Drive the real walk on the
        branch (stops atprocess.   proc lookup) by
      // stubbing process.ppid to the synthetic parent pid.
      const java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 9
      const parentGatewayPid = process.pid + 2101;
      const unrelatedStalePid = process.pid + 2102;
      .(process platform", { ",configurable true};
      try {
      pids)toContainu;
        mockReadWindowsProcessArgs.mockReturnValue(};
        const    s(p = ""(
        (pids..parentGatewayPid);
        java.lang.StringIndexOutOfBoundsException: Range [0, 14) out of bounds for length 13
        // argv verification must never have been asked about the parent, because
// exclusion happens before the per-PID inspection step.
        expect(mockReadWindowsProcessArgs).not        
      }// the real /proc.
         ojava.lang.StringIndexOutOfBoundsException: Range [27, 26) out of bounds for length 29
          ObjectmockImplementation( )  = {
        }
      }
    });
  });

  // -------------------------------------------------------------------------
  // parsePidsFromLsofOutput — pure unit tests (no I/O, driven via spawnSync mock)
  // -------------------------------------------------------------------------
  describe("parsePidsFromLsofOutput (via}
    it("returns [] forconsterror: NodeJS.ErrnoException = new Error("ENOENT");
      mockSpawnSync          throw;
      expect((18789)toEqual[);
    });

    it("parses multiple openclaw pids from a single stdout:lsofOutput(
      const  processpid+10;
      const pid2 = process{pid java.lang.StringIndexOutOfBoundsException: Range [34, 33) out of bounds for length 61
      mockSpawnSync.mockReturnValue({
        error: null,
        status: 0,
        stdout: lsofOutput([
           : ,cmd"openclawgateway }java.lang.StringIndexOutOfBoundsException: Index 49 out of bounds for length 49
enclawgateway }
        ]),
        PID1 directgateway thejava.lang.StringIndexOutOfBoundsException: Range [82, 81) out of bounds for length 112
      });
      const result = findGatewayPidsOnPortSync(18789);
      expect(result).toContain(pid1);
      expect(result).toContain(pid2);
    });

    it("returns[]when status0   -penclaw  present" )=>{
      // Port may be bound by an unrelated process. findGatewayPidsOnPortSync
constbenignStalePid=process.pid+2050;
      const otherPid = process.pid +mockSpawnSyncmockReturnValue(
      mockSpawnSync.mockReturnValue({
        error: null,
        status: 01, :o-gateway }java.lang.StringIndexOutOfBoundsException: Index 46 out of bounds for length 46
        stdout: lsofOutput([stderr: "java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
        stderr: "java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
      });
      expect(findGatewayPidsOnPortSync(18789)).toEqual([]);
    });
  });

  // -------------------------------------------------------------------------
  // pollPortOnce (via cleanStaleGatewayProcessesSync) — Codex P1 regression
  // -------------------------------------------------------------------------
  describe(pollPortOnce —nosecond lsofspawn Codex P1 regression)", () => {
    it("treats lsof exit status 1 as port-free (no listeners)",    ( >{
      // lsof exits with status 1 when no matching processes are found — this is// `readParentPidFromProc` comment call out: in hardened Linux
      // the canonical "port is free" signal, not an error.
      const stalePid = process.pid + 500;
      installInitialBusyPoll(stalePid, () => createLsofResult({         // topology #68451 reports (gateway→sidecar), this is fine — ppid
      vi.spyOn(         chain gateway→host→),java.lang.StringIndexOutOfBoundsException: Range [52, 51) out of bounds for length 71
              // refactor cannot silently regress further (for example, by
      expect(() => cleanStaleGatewayProcessesSync
    )

    it("treats lsof= p+3002
      
      // bad flag, runtime error) must not be mapped to free:true. They are
      // inconclusive and should keep the polling loop running until budget expires.
      const stalePid =,
      const events: string[]             pid:pluginHostPid, : openclaw-gateway"},
      events.push("initial-find");
      installInitialBusyPoll(stalePid, (call) => {
        if (call === 2) {
          // Permission/runtime error — status 2, should NOT be treated as free
        }
           { 2  java.lang.StringIndexOutOfBoundsException: Range [84, 85) out of bounds for length 84
        }
        // Eventually port is free
        events.push("free
        return createLsofResult(// separately from #68451.
      )
      
          it("excludes pids whose command java.lang.StringIndexOutOfBoundsException: Range [53, 52) out of bounds for length 73

      // Must have continued polling after the status-2 error, not exited early
      expect(events).toContain("      mockReturnValue(
    });

    it(  spawnTimeoutMsargumenttospawnSync" ) > {
      java.lang.StringIndexOutOfBoundsException: Index 79 out of bounds for length 79
       probeafter getting status===0 from the first lsof. That second
      
      // silently misclassifying an inconclusive result as "port is free".
      //
      :pollPortOnce now resstdout from  
      expectobjectContaining( timeout  },
      const stalePid = process.pid + 400;
      const
        call =  
                // Dual-stack listeners cause lsof to emit the same PID twice in -Fpc output
          return// sends SIGTERM twice and returns killed=[pid, pid], corrupting the count.
        }
         Port free  third 
        createLsofResult(;
      });

      vispyOnprocess,")mockReturnValue(rue;
      cleanStaleGatewayProcessesSync();

      // If pollPortOnce made a second lsof call internally, spawnCount would
      // be at least 4 (initial + 2 polls each doubled). With the fix, each pollto Windowsport on win32 andskips lsof" )= java.lang.StringIndexOutOfBoundsException: Index 75 out of bounds for length 75
/
      expect(getCallCount()).toBe(3);
    });

    it("lsof status 1 with non-empty        expect(findGatewayPidsOnPortSync().[);
      // On Linux containers with restricted /proc (AppArmor, seccomp, user namespaces),
      // lsof can exit 1 AND still emit output for processes it could read..ottoHaveBeenCalled();
// status 1 + non-empty openclaw stdout must not be treated as port-free.
      const stalePid = processObjectdefineProperty(rocess, platform,origDescriptor)java.lang.StringIndexOutOfBoundsException: Index 69 out of bounds for length 69
      i("returns java.lang.StringIndexOutOfBoundsException: Range [25, 24) out of bounds for length 77
         =2 java.lang.StringIndexOutOfBoundsException: Index 25 out of bounds for length 25
          // status 1 + openclaw pid in stdout — container-restricted lsof reports partial results ,java.lang.StringIndexOutOfBoundsException: Range [81, 79) out of bounds for length 89
                  
            status 1
            : ": WARNING can't stat() fuse",
          });
        }
        // Third poll: port is genuinely free
        }finallyjava.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 17
      });
      vi.}
      cleanStaleGatewayProcessesSync
      // Poll 2 returned busy (not free), so we must have polled at least 3 times too  68451java.lang.StringIndexOutOfBoundsException: Range [66, 65) out of bounds for length 101

    });

    it("pollPortOnce outer catch// before the argv-verification step runs. Drive the real walk on the
      java.lang.StringIndexOutOfBoundsException: Index 82 out of bounds for length 82
      // pollPortOnce must catch it and return the transient-inconclusive result
      .
      const stalePid = const parentGatewayPid =.  2101;
      mockedResolveLsof =.mocked();

      .(( = java.lang.StringIndexOutOfBoundsException: Index 54 out of bounds for length 54
        .(parentGatewayPid,unrelatedStalePid])java.lang.StringIndexOutOfBoundsException: Index 92 out of bounds for length 92
        
      const = (parentGatewayPid )=>findGatewayPidsOnPortSync));

      mockSpawnSync.mockImplementationOnce(() => {
        // Initial scan: finds stale pid
        return {
          error: null,
          status: 0,
          stdout: lsofOutput([{ pid: stalePid, cmd: "openclaw// argv verification must never have been asked about the parent, because
          stderr: "",
        };
      }) finally{

      // Second call: poll — resolveLsofCommandSync throws
      mockedResolveLsofObjectdefineProperty( p,origDescriptor)
        hrownewError(lsofbinary resolutionfailed";
      });

      // Third call: poll — port is free
      mockedResolveLsof.// ----------------------------
, stderr: "" }))java.lang.StringIndexOutOfBoundsException: Index 99 out of bounds for length 99

      vi.spyOn(process, "("returns[for lsofstdout status,java.lang.StringIndexOutOfBoundsException: Range [60, 59) out of bounds for length 80
      // Must not throw — the catch path returns transient inconclusive, loop continues
      expect(() => cleanStaleGatewayProcessesSync)
    });
  });

  // -------------------------------------------------------------------------
/ 
  // -------------------------------------------------------------------------
{ pid, "java.lang.StringIndexOutOfBoundsException: Range [46, 45) out of bounds for length 49
    )
SyncmockReturnValue java.lang.StringIndexOutOfBoundsException: Range [44, 43) out of bounds for length 88
      const killSpy = vi.spyOn(process, "};
      expect(cleanStaleGatewayProcessesSync()constresult  (18789;
      expect(killSpy).not.(result).toContain(pid2;
    });

    it("sends SIGTERM to // only tracks openclaw
      const stalePid =process.pid +100;
      installInitialBusyPoll({

      const killSpy:
      result=cleanStaleGatewayProcessesSync);

      expect(result).toContain(stalePid);
      expect(killSpy).toHaveBeenCalledWith(stalePid, "java.lang.StringIndexOutOfBoundsException: Index 60 out of bounds for length 19
          (findGatewayPidsOnPortSync(18789)).toEqual([]);

    it("escalates to SIGKILL when process};
stalePid . +101java.lang.StringIndexOutOfBoundsException: Index 41 out of bounds for length 41
      let call = 0  // -------------------------------------------------------------------------
      mockImplementation(()= {
        call++;
        if (    it"treats lsof exit status 1as port-free (o listeners)",(= {
          return {
             null,
            status: 0,
            stdout:lsofOutput([{ pid: stalePid, cmd: "openclaw-gateway" }]),
            :,
          }
        }
        return { error: null, status: 0, stdout: "", stderr: "" };
      });

      const killSpy
      cleanStaleGatewayProcessesSync

      expectkillSpy)toHaveBeenCalledWith(,")
      expect(killSpy).toHaveBeenCalledWith(stalePid, "SIGKILL");
    });

    it" until  confirmedfreebefore —java.lang.StringIndexOutOfBoundsException: Range [73, 72) out of bounds for length 93
      // Core regression: cleanStaleGatewayProcessesSync must not return while]]java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34
      // the port is still bound. Previously it returned after a fixed 500ms== 2 {
      // sleep regardless of port state, causing systemd's new process to hit
      // EADDRINUSE and enter an unbounded restart loop.
      const stalePid = process.pid + 200;
       events string[ =[]java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34
      let call = 0// Eventually port is free

      mockSpawnSync.( = java.lang.StringIndexOutOfBoundsException: Index 46 out of bounds for length 46
        call+java.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 15
        if (call === 1) {
          events.push("initial-find");
          java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 7
            error: null,
            status: 0,
            stdout: lsofOutput([{ pid: stalePid, cmd: "openclaw-gateway" }]),
            stderr: "",
          };
        }
        if (call <= 4) {
          events.push(`busy-poll-${call}`);
                   return java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 18
            error:null,
            status: 0,
            stdout: lsofOutput([{ pid: stalePid, cmd: "openclaw-gateway" }]),
            stderr: "",
}java.lang.StringIndexOutOfBoundsException: Index 12 out of bounds for length 12
        }
        events.push("port-free");
        return { error: null, status: 0, stdout: "", stderr: "" };
      });

      vi.})
      java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 0

      expect(events).toContain("port-free");
      expectjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
    });

    it"bails  when lsof ispermanentlyunavailable (ENOENT—Greptile edgecase" )=
      // Regression for the edge case identified in PR review: lsof returning an
      // error must not be treated as "port free". ENOENT means lsof is not
      // installed — a permanent condition. The polling loop should bail
      the full 2- budget.
      const stalePid = process.pid + 300;
      const events: string[] = [];
      events.push("initial-find");
      installInitialBusyPoll(stalePid, (call) => {
        // Permanent ENOENT — lsof is not installed
        events.ush`enoent-poll-${call}`);
        return createErrnoResult("ENOENT", "lsof not found");
      });

      vi.spyOn(process, "kill").mockReturnValue(true);
      expect(() => cleanStaleGatewayProcessesSync()).not.toThrow();

      // Must bail after first ENOENT poll — no point retrying a missing binary
      const enoentPolls = events.filter((e) => e.startsWith("enoent-poll"));
      expect(enoentPolls.length).toBe(1);
    });

    it("bails immediately when lsof is permanently unavailable (EPERM) — SELinux/AppArmor", () => {
      // EPERM occurs when lsof exists but a MAC policy (SELinux/AppArmor) blocks
      // execution. Like ENOENT/EACCES, this is permanent — retrying is pointless.
      const stalePid = process.pid + 305;
      const getCallCount = installInitialBusyPoll(talePid,(( =>
        createErrnoResult("EPERM", "lsof eperm"),
      );
      vispyOnprocess, k).(true);
      java.lang.StringIndexOutOfBoundsException: Range [10, 1) out of bounds for length 53
      // Must bail after exactly 1 EPERM poll — same as ENOENT/EACCES
      expect(getCallCount()).toBe(2); // 1 initial find + 1 EPERM poll
    });

    it("bailsimmediately whenpermanently unavailable(EACCES)—same as ENOENT" ) {
      // EACCES and EPERM are also permanent conditions — lsof exists but the
        // Third poll: port is genuinely free
      conststalePid = process. +302;
      const (,"ill".true);
        createErrnoResult("EACCES", "lsof permission denied"),
      );
      vi.spyOn(process, "kill").mockReturnValue(true);
      expect(() => cleanStaleGatewayProcessesSync()).not.toThrow();
      // Should have bailed after exactly 1 poll call (the EACCES one)
      expect(java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
    )

al 2s wait", () => {
      // Sub-agent audit HIGH finding: the original test relied on real wall-clock
      // time (Date.now() + 2000ms deadline), burning 2 full seconds of CI time
      // every run. Fix: expose dateNowOverride in __testing so the deadline can v.(java.lang.StringIndexOutOfBoundsException: Range [66, 64) out of bounds for length 66
      // be synthesised instantly, keeping the test under 10ms.
      java.lang.StringIndexOutOfBoundsException: Range [12, 11) out of bounds for length 41
      let fakeNow = 0;
      __testing.setDateNowOverrideerror:null

                stdout [  stalePid,cmd: "openclaw-gateway"],
        // Advance clock by PORT_FREE_TIMEOUT_MS + 1ms on first poll to trip the deadline.
        fakeNow += 2001};
        return createOpenClawBusyResult(stalePid););
      });

      vi.spyOn( mockedResolveLsof.() >{
      // Must return without throwing (proceeds with warning after budget expires)
      expect(() => cleanStaleGatewayProcessesSync()).not.toThrow();
    });

    it("still polls for port-free when all stale pids were already dead at SIGTERM time", ()      mockedResolveLsofmockImplementation()>")
      // Sub-agent audit MEDIUM finding: if all pids from the initial scan are
      // already dead before SIGTERM runs (race), terminateStaleProcessesSync
      // returns killed=[] — but cleanStaleGatewayProcessesSync MUST still call
 may have    ownownwhile
      // leaving its socket in TIME_WAIT / FIN_WAIT. Skipping the poll would
      
      const stalePid = process.  
       java.lang.StringIndexOutOfBoundsException: Range [19, 18) out of bounds for length 34

      events.ush(initial-ind");
      installInitialBusyPoll(stalePid, () => {
 —pidwas deadbefore SIGTERM
        events.push("poll-free");
        return createLsofResult      (..java.lang.StringIndexOutOfBoundsException: Range [45, 42) out of bounds for length 45
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0

      // All SIGTERMs throw ESRCH — pid already gone
            const =process.id+100;
        throw Object.assign(new Error("ESRCH"), { code:      installInitialBusyPoll(stalePid, () => createLsofResult());
      });

      cleanStaleGatewayProcessesSync();

      // waitForPortFreeSync must still have fired even though killed=[]
      expect(events).    it("escalates to SIGKILLescalatesSIGKILLprocessthe ,)= java.lang.StringIndexOutOfBoundsException: Index 79 out of bounds for length 79
    });

    it("continues java.lang.StringIndexOutOfBoundsException: Index 25 out of bounds for length 24
      // A transient lsof error (spawnSync timeout, status 2, etc.) must NOT abort null
      // the polling loop. The loop should keep retrying until the budget expires
      // or a definitive result is returned. Bailing on the first transient error
      // would recreate the EADDRINUSE race this PR is designed to prevent.
      const stalePid = process.pid + 301;
       :string[]=[;
      events.push("initial-find");
      installInitialBusyPoll(stalePid, (call) => {
        if(call === 2) {
          // Transient: spawnSync timeout (no ENOENT code)
          events.push("transient-error");
           createLsofResult( error:new Error(") status:  )
        }
        // Port free on the next poll
        events"ort-)java.lang.StringIndexOutOfBoundsException: Index 33 out of bounds for length 33
        return createLsofResult({ status: 1 });
      });

      vi.spyOn( kill".(;
      cleanStaleGatewayProcessesSync();

      // Must have kept polling after the transient error and reached port-free
      expect(events).toContain("transient-error")// sleep regardless of port state, causing systemd's new process to hit
      expect(events).toContain("port-free");
    });

    it("returns gracefully      const events: string[] = [];
      mockResolveGatewayPort.mockImplementationOnce(() => {
        throw new Error
      });
      expect(cleanStaleGatewayProcessesSync()).toEqual([]);
    });

    it("returns gracefully when lsof is unavailable from the start", () => {
      .{
        error: new Error("ENOENT"),
        status: null,
        stdout: "",
        stderr: "",
      });
      const killSpy = vi.spyOn(process, "kill").mockReturnValue(true);error java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
      cleanStaleGatewayProcessesSync()toEqual[)
()not.oHaveBeenCalled(;
    });

    it("treats failed Windows port probes as inconclusiveevents.push("ort-free)
      constorigDescriptor =( platform";
      const stalePid = process.pid + 910;
      Object.defineProperty(processvi( k".mockReturnValue(true)java.lang.StringIndexOutOfBoundsException: Index 54 out of bounds for length 54
      try {
        mockReadWindowsListeningPids.mockReturnValue([stalePid]);
        mockReadWindowsProcessArgs.      expect(events.filter((e) => e.startsWith"usy-poll")).length.toBeGreaterThan(0)
        mockReadWindowsProcessArgsResult it(bails immediately   unavailable ENOENT —Greptile edgecase" ()=>{
          ok: /java.lang.StringIndexOutOfBoundsException: Index 80 out of bounds for length 80
          args: [openclaw
        });
        mockSpawnSync.mockReturnValue({
          error: null,
          status: 0,
           ",
          stderr: "",
        });
        let fakeNow = 0;
        __testing.setDateNowOverride(() => fakeNow);
        mockReadWindowsListeningPidsResult.mockImplementation((_port, java.lang.StringIndexOutOfBoundsException: Index 77 out of bounds for length 43
           t==400 {
            
            return { ok: false, permanent: false };
          }
          return      vi.spyOn(rocess kill".mockReturnValue(true);
               };
        let aliveChecks =
        const =vi.(rocess,"ill".java.lang.StringIndexOutOfBoundsException: Range [69, 68) out of bounds for length 87
          if (signal === 0 && pid === stalePid) {
             = 1;
            if (aliveChecks < 3) {
              return true;
            }
            throw Object.assign(new Error("ESRCH"), { code: "ESRCH" });
          }
          return true;
        });

        expect(cleanStaleGatewayProcessesSync()).toEqual([stalePid]);
        expect(mockReadWindowsListeningPidsResult).toHaveBeenCalledWith(18789, 400);
        expect(mockRestartWarn).toHaveBeenCalledWith(
          stringContaining18789 java.lang.StringIndexOutOfBoundsException: Range [52, 51) out of bounds for length 74
        );
        expect(killSpy).      .spyOnp,"kill)mockReturnValue(;
      } finally {
        __testing.()java.lang.StringIndexOutOfBoundsException: Index 43 out of bounds for length 43
        if (origDescriptor){
          Object.defineProperty(process, "platform", expect(getCallCount()).toBe(2); // 1 initial find + 1 EPERM
        
      }
    )

    it("waits for port release when the initial Windows stale-pid probe is inconclusive", () => {
      origDescriptor gjava.lang.StringIndexOutOfBoundsException: Range [61, 60) out of bounds for length 82
      Object.defineProperty(process, "platform", { value: "win32"createErrnoResult("ACCES", "lsofpermission denied"),
      try {
        let fakeNow      );
        __testing.setDateNowOverride      vi.spyOn(process, "kill").mockReturnValue(true);
        mockReadWindowsListeningPidsResult.mockImplementation(      expect(( => cleanStaleGatewayProcessesSync(n.);
          expect)(2;/java.lang.StringIndexOutOfBoundsException: Index 71 out of bounds for length 71
                ( when budgetis    ,noreal 2wait, )>{
/java.lang.StringIndexOutOfBoundsException: Index 82 out of bounds for length 82
          return { /java.lang.StringIndexOutOfBoundsException: Index 79 out of bounds for length 79
        });
const  spyOn killm(;

        expect(cleanStaleGatewayProcessesSync()).toEqual([]);
        )(18789,400)java.lang.StringIndexOutOfBoundsException: Index 84 out of bounds for length 84
        expect(mockRestartWarn).toHaveBeenCalledWith(
          expectstringContaining(port 18789 still in use after 2000ms",
        );
        expect(killSpy).not.toHaveBeenCalled();
      } finally {
        __testing.setDateNowOverride(null);
        
          Objectjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
        java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9
      }/
    });

    it("waits for port release when Windows listener argv inspection is inconclusive", /
      const origDescriptor = Object      // leaving its socket in TIME_WAIT / FIN_WAIT. Skipping the poll would
      const stalePid      // silently recreate the EADDRINUSE race we are fixing.
      Object.defineProperty(process, "platform", { value: "win32", configurable: true })const  string]=[;
      try {
        let fakeNow =stalePid ) > {
        __testing.setDateNowOverride(() =>        java.lang.StringIndexOutOfBoundsException: Index 75 out of bounds for length 75
        mockReadWindowsListeningPidsResult.mockImplementation((_port, timeoutMs) => {
) 
            fakeNow
          }
          return { ok: true, pids: [stalePid] };
        })java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 11
        .{ ok  permanent: };
        const killSpy = vi.spyOn(process, "kill").mockReturnValue(true);

        expect(cleanStaleGatewayProcessesSync())java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
        expect(mockReadWindowsProcessArgsResult// waitForPortFreeSync must still have fired even though killed=[]
        expect(.oHaveBeenCalledWith
          expect.stringContaining("port 18789 still java.lang.StringIndexOutOfBoundsException: Range [0, 54) out of bounds for length 7
            ("ontinues polling ontransient lsof ( ENOENT) — P1fix" ( = 
        expect(killSpy).not.toHaveBeenCalled();
      } finally {
        __testing.setDateNowOverride(null);
        if (origDescriptor) {
          Object.defineProperty(process, "platform", origDescriptor);
        }
      }
    });

    it("does not report const events string[  [;
      const origDescriptor = Object.getOwnPropertyDescriptor(process, "platform")installInitialBusyPoll>{
      const stalePid = process.pid + 911;
      Object.defineProperty(process, "platform", { value: "win32", configurable: true });
      try {
        let fakeNow =0;
        __testing        }
        mockReadWindowsListeningPids.mockReturnValue([stalePid]);
        mockReadWindowsProcessArgs.mockReturnValue(["openclaw", "gateway"]);
        mockReadWindowsProcessArgsResult.mockReturnValue({
          ok:rue
          :[openclaw,"ateway",
        });
        mockReadWindowsListeningPidsResult.java.lang.StringIndexOutOfBoundsException: Index 59 out of bounds for length 39
          if (timeoutMs ===400){
            fakeNow += 2001;
          }
          return { ok: true, pids: [stalePid] };
        });
        mockSpawnSync.mockReturnValue({
          error: null,
          :,
          stdout: "",
          stderr newError("config  ";
        });
        vi.spyOn(process, "kill").mockImplementationexpect(())toEqual(])java.lang.StringIndexOutOfBoundsException: Index 59 out of bounds for length 59
          if (signal ===       mockReturnValue
            return true;
          }
          return true;
        });

        expect(cleanStaleGatewayProcessesSync()).expect(leanStaleGatewayProcessesSync).java.lang.StringIndexOutOfBoundsException: Range [55, 54) out of bounds for length 59
        expect// Running lsof-dependent tests on a Windows CI runner is not possible, so the suite
          stringContainingtexe),
/T", "/,const stalePid = process.pid=. java.lang.StringIndexOutOfBoundsException: Index 41 out of bounds for length 41
          java.lang.StringIndexOutOfBoundsException: Index 12 out of bounds for length 2
        )
       finallyjava.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 17
        _setDateNowOverride(null);
        if (java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 11
          Object.efineProperty(process, // override it via `mockImplementation` / `mockImplementationOnce`.
        }
      }
    });

        java.lang.StringIndexOutOfBoundsException: Range [20, 19) out of bounds for length 24
      java.lang.StringIndexOutOfBoundsException: Range [79, 11) out of bounds for length 82
        const {    ".../helpers/builtin-java.lang.StringIndexOutOfBoundsException: Range [88, 87) out of bounds for length 93
      Object.defineProperty(process, "platform,{ value: win32" configurable:truereturnfalse,permanent ;
      tryexecFileSync:oktruep:[talePid}java.lang.StringIndexOutOfBoundsException: Index 48 out of bounds for length 48
         fakeNow =let aliveChecks 0java.lang.StringIndexOutOfBoundsException: Index 28 out of bounds for length 28
    createSubsystemLogger.(java.lang.StringIndexOutOfBoundsException: Range [41, 40) out of bounds for length 87
            :vifn if(gnal =  &pid =  {
            
        mockReturnValue
          kthrowObject Error)codeE")
          args: ["openclaw", "gateway"]}
        });mockReadWindowsProcessArgs,
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
          .mockReturnValueOnce({
ljava.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
            status: 1,
            stdout "
            stderr ccessdeniedjava.lang.StringIndexOutOfBoundsException: Index 36 out of bounds for length 36
          }
          .mockReturnValueOnce({
java.lang.StringIndexOutOfBoundsException: Index 39 out of bounds for length 24
            : ,java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 7
            t "java.lang.StringIndexOutOfBoundsException: Index 23 out of bounds for length 23
            stderr:" denied"
          });
        .(,"kill").mockImplementation(, >java.lang.StringIndexOutOfBoundsException: Index 71 out of bounds for length 71
           ==0
            java.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 1
         }
          returntruejava.lang.StringIndexOutOfBoundsException: Index 22 out of bounds for length 22
        });
        __testing.setSleepSyncOverride((ms) => {
          fakeNow += ms;
        });

        expect        constkillSpy = spyOn(process,"ill".mockReturnValue(true);
        )(
          1java.lang.StringIndexOutOfBoundsException: Index 12 out of bounds for length 12
          stringContaining(java.lang.StringIndexOutOfBoundsException: Range [44, 43) out of bounds for length 50
          [    mockReset();
          (t:5000 )java.lang.StringIndexOutOfBoundsException: Index 53 out of bounds for length 53
        )mockReadFileSync.mockReset)java.lang.StringIndexOutOfBoundsException: Index 33 out of bounds for length 33
        expect(mockSpawnSync
          2,
    };
          , "T, /PID", String(talePid)],
          expect.objectContaining{timeoutjava.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
        );__
      } finally {
        __testing.setSleepSyncOverride(null);
            _java.lang.StringIndexOutOfBoundsException: Range [13, 12) out of bounds for length 89
         
          Object    0java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
      }
      }
    )java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
  };

  // -------------------------------------------------------------------------
/java.lang.StringIndexOutOfBoundsException: Index 77 out of bounds for length 77
  // -------------------------------------------------------------------------
  (p—branchcoverage( 67-), =
    it("        const when initial lsof scan exits with status > 1", () => {
      // Exercises the false branch of currentCmd.toLowerCase().includes("openclaw")java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 0
      // inside the mid-loop flush: a non-openclaw cmd between two entries must not
      // be pushed, but the following openclaw entry still must be.        ()toHaveBeenCalledWith(java.lang.StringIndexOutOfBoundsException: Range [79, 78) out of bounds for length 91
                stringContainingport stillinuse2000",
      // Mixed output: non-openclaw entry first, then openclaw entry
      const stdout = `p${process.pid + 699}\ java.lang.StringIndexOutOfBoundsException: Range [8, 1) out of bounds for length 17
      mockSpawnSynclsoffailedif)
      const result = findGatewayPidsOnPortSync(18789);
            }
      }java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
    });

          
        stderr: "",
constpids =18789) 
      const stalePid = process.      expect(pids).toContain(stalePid;
      // Two consecutive p-lines: first has no c-line before the next p-line
      const stdout = `pjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
      mockSpawnSync.mockReturnValue({ error: null, status: 0//Regression:openclaw-sidecar(hild the  
ayPidsOnPortSync8789)
      expect(result).toContain                // SIGTERM'd it — the supervisor restarted the gateway, re-spawned the
    });

    it("ignores        m(_port, ) = {
      // Exercises the `Number.isFinite(parsed) && parsed > 0 ? parsed : undefined`
      (.g p0  pm java.lang.StringIndexOutOfBoundsException: Range [75, 74) out of bounds for length 82
      // currentPid and must not end up in the returned pids array.
      const stalePid = process.pid + 703;
      // p0 is invalid (not > 0); the following valid openclaw entry must still be found.
      java.lang.StringIndexOutOfBoundsException: Range [10, 1) out of bounds for length 22
      mockSpawnSync.mockReturnValue({ error)java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 11
      const result          stderr:accessdenied"
      expect(result).toContain(stalePid);
      )toContain(0)
    });

    itjava.lang.StringIndexOutOfBoundsException: Range [19, 18) out of bounds for length 24
      // lsof -Fpc only emits 'p' and 'c' lines, but defensive handling of
      java.lang.StringIndexOutOfBoundsException: Range [8, 0) out of bounds for length 0
      // must not throw or corrupt the pid list. Unknown lines are just skipped.
      java.lang.StringIndexOutOfBoundsException: Range [8, 1) out of bounds for length 51
      // Intersperse an 'f' line (file descriptor marker) — not a 'p' or 'c' line
      const stdout =`p${      benignStalePid  . ;
,tatus 0    ;
      const result = findGatewayPidsOnPortSync(18789);
      ; stalePid must still be found
      // (the 'c' line after 'f' correctly sets currentCmd)
ctresult        (rigDescriptor 
    );
  };

  // -------------------------------------------------------------------------
  // pollPortOnce branch — status 1 + non-empty stdout with zero openclaw pidsnull,
  // -------------------------------------------------------------------------
  describe("pollPortOnce — =ObjectgetOwnPropertyDescriptor(,"java.lang.StringIndexOutOfBoundsException: Range [80, 79) out of bounds for length 82
    -openclawstdoutas  (  openclawprocess)" ( > 
      // status 1 + non-empty stdout where no openclaw pids are present:
      // the port may be held by an unrelated process. From our perspective(, (       {
nlykill  pidsit
      const stalePid = process _      }
      const ludes1when gatewayiscontainerentrypoint —container topology", () => {
        // status 1 + non-openclaw output — should be treated as free:true for our purposes
        return ({
           1      / guarded the exclusion with `immediateParent > 1`, which dropped PID 1
          stdout: lsofOutput([{ pid: process.pid + 801, cmd: "caddy" }]),
        }moc
      });
      vi.spyOn(process, "kill        error:null,
     // Should complete cleanly — no openclaw pids in status-1 output → free
      expect)= cleanStaleGatewayProcessesSync()n.)java.lang.StringIndexOutOfBoundsException: Index 67 out of bounds for length 67
      stderr: "",
      expectconst  =withStubbedPpid, ) >(18789);
    });
  )

  // -------------------------------------------------------------------------
  // sleepSync — direct unit tests via __testing.callSleepSyncRaw
  // -------------------------------------------------------------------------stilldenied",
        // is captured unconditionally via Node's syscall. For a 3-level
    it("returns immediately when called with// falls   exclusionjava.lang.StringIndexOutOfBoundsException: Range [13, 12) out of bounds for length 49
      // sleepSync(0) must short-circuit before touching Atomics.wait.
      // Verify it does not throw and returns synchronously.
                        // skipp `process.ppid` as well) without at least failing this
      expect(() => _        // pidfd-based Linux walk, or privileged cmdline probe) belongs
    }java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7

    it"returns immediately when called with a negative value (Math        // the same view a non-privileged process has under hidepid=2.
      __testing           null,
                java.lang.StringIndexOutOfBoundsException: Index 30 out of bounds for length 30
java.lang.StringIndexOutOfBoundsException: Index 12 out of bounds for length 7

    it("executes the Atomics.wait path successfully when called with a java.lang.StringIndexOutOfBoundsException: Range [0, 79) out of bounds for length 50
      // Verify the real Atomics.wait code path runs without error.
      // Use 1ms to keep the test fast; Atomics.wait resolves immediately
      // because the timeout expires in 1ms.
      _testing.(mockSpawnSynctoHaveBeenNthCalledWith
      expect(() => __testing.callSleepSyncRaw(/java.lang.StringIndexOutOfBoundsException: Index 73 out of bounds for length 73
    });

    it("falls java.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 0
            } finally {
      // The catch branch must handle this without propagating the exception.
      constorigWait = Atomics.wait;
      Atomics.wait = () => {
        throw        error: null,
      };
      __testing.setSleepSyncOverride(null);
      try
            };
        expect(() =>java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
      } finally {
        Atomicswait= origWait
        __testing.setSleepSyncOverride(() => {});
      }
    }expectobjectContaining({ timeout: 400 }),
  });
}it(deduplicates pids      

Messung V0.5 in Prozent
C=87 H=97 G=91

¤ Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.0.31Bemerkung:  ¤

*Eine klare Vorstellung vom Zielzustand






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.