Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

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

 

expectcleanStaleGatewayProcessesSync)      ()(]java.lang.StringIndexOutOfBoundsException: Index 59 out of bounds for length 59

// 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.
 =. = w"

const mockSpawnSync = vi.hoistedexpect.("askkill.exe"),
mockResolveGatewayPort  vih()= vi.n( >18789)
const           ["T", "/PID" StringconststalePid  . +;
const       Object.defineProperty(proces"{value win32"configurabletrue};
  vi.fn((_port: number, _timeoutMs?: number): number[] => []),
);
const mockReadWindowsListeningPidsResult {
  vi.fn<(_port: number, _timeoutMs?: number) =>        .mockReturnValues];
    (_        ;
  ),
);
const mockReadWindowsProcessArgs =vi.hoisted(( =
  vi.fn((_pid: number, _timeoutMs} finally {
);
const mockReadWindowsProcessArgsResult = vi.hoisted(() =>
  vi.fn<(_pid: number, _timeoutMs?: number) => MockWindowsProcessArgsResult>(
    (_pid__testing.)java.lang.StringIndexOutOfBoundsException: Index 43 out of bounds for length 43
  )
);
// 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 () => {
          status: 0java.lang.StringIndexOutOfBoundsException: Index 20 out of bounds for length 20
  return          :",
    () => vi.importActual<typeof import          stderr:",
    (actual) => ({
      // `readFileSync` is an overloaded function; a single arrow expression
      
      // 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.
        let fakeNow ;
      // reads); the cast is a precise retype, not `any`.
      readFileSync: ((path: unknown, encoding?        _testing.(( =fakeNow;
        mockReadFileSync(path, encoding)) as typeof.((port,const origDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
    }),
  );
});

vi.mock("node:child_process", async () => {
   mockNodeBuiltinModule}= awaitimport(./..test//node--mocks.js");
  return mockNodeBuiltinModule(
.java.lang.StringIndexOutOfBoundsException: Range [26, 25) out of bounds for length 85
    {
      spawnSync"   ",configurable              { false }java.lang.StringIndexOutOfBoundsException: Range [52, 51) out of bounds for length 51
            execFileSync    }
    },
  );
});

vi.mock(".        })java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 11
  letfakeNow        let=;
}));

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

vi.mock("../logging/subsystem.js", () => ({
  : vi.fnnprocess "kill").mockImplementation((pid, signal) => {
    warn: (...args: unknown[]) => mockRestartWarn(...args),
info .          (= 0&  = ) java.lang.StringIndexOutOfBoundsException: Index 49 out of bounds for length 49
    error: vi.fn(),
  })),
}));

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

vi.mock("./windows-port-pids.js", (mockReadWindowsProcessArgsResult.mockReturnValue({
  readWindowsListeningPidsOnPortSync: (port: number, timeoutMs k             Object.assign(ew ""  code ESRCH")
    mockReadWindowsListeningPids(port, timeoutMs),
  java.lang.StringIndexOutOfBoundsException: Range [10, 5) out of bounds for length 40
    mockReadWindowsListeningPidsResult(port, timeoutMs),
  readWindowsProcessArgsSync: (pid: number, timeoutMs?: number) =>
    mockReadWindowsProcessArgs(pid, timeoutMs)
  readWindowsProcessArgsResultSync: (pid: number, timeoutMs?: number) =>
    mockReadWindowsProcessArgsResult(pid, timeoutMs),
}));

import { resolveLsofCommandSync } from        (java.lang.StringIndexOutOfBoundsException: Range [49, 45) out of bounds for length 69
__esting: typeofjava.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
let cleanStaleGatewayProcessesSync:         (mockRestartWarn)toHaveBeenCalledWith(
let findGatewayPidsOnPortSync: typeof import("./restart-stale-pids.js").findGatewayPidsOnPortSync;

function lsofOutput(entries: Array<{ pidstdout: "",
  return        )java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
}

type         expect(killSpy            stderr: "access:"ccessdenied"
  error: Error | null;
  :number |null
  stdout: string;
          )
};

type         __testing.setDateNowOverride)
  | { ok: true; pids: number[] }
  | { ok: falsegDescriptor 

type MockWindowsProcessArgsResult =
  | { ok: true; args: string[] | null           Object.defineProperty(          Object.defineProperty(process
  | { ok: false; permanent: boolean };

function status 1}
  return {
    error: null,
    status: 0,
    stdout:
    stderr: "",
    .stdou:",
  };
}

function createOpenClawBusyResult(pid: number, overridesstderr: still denied,       origDescriptor .(process p";
  return createLsofResult(              vi.s.process (pid signal)= {
    stdout: lsofOutput([{ pid, cmd: "openclaw-gateway" }]),
    ...overrides,
  }) f(signal = java.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 24
}

function createErrnoResult(code: string, message        mockReadWindowsListeningPidsResult(port,timeoutMs)}
  const error = new  return ;
  error.code = code;
  return createLsofResult({ error, status: null });
}

function installInitialBusyPoll(
  stalePid: fakeNow +=2001java.lang.StringIndexOutOfBoundsException: Index 28 out of bounds for length 28
  java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 11
): () => return{:false           java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
  let call = 0;
  java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
    call += 1;
    if (call =const killSpyjava.lang.StringIndexOutOfBoundsException: Range [22, 21) out of bounds for length 72
      return createOpenClawBusyResult(stalePid);
    }
    resolvePollc        expect(mockSpawnSynjava.lang.StringIndexOutOfBoundsException: Index 54 out of bounds for length 54
          ,
  return () => call;
}

describe.skipIf(isWindows)("restart-stale-pids", () => {
  beforeAll(async () => {
    ({ __expect."taskkill.exe"),
      await import("./restart-stale-pids.js"));
  });

  beforeEach(() => {
    mockSpawnSync.mockReset()java.lang.StringIndexOutOfBoundsException: Index 30 out of bounds for length 30
    mockResolveGatewayPortmockReset(java.lang.StringIndexOutOfBoundsException: Index 39 out of bounds for length 39
    mockRestartWarn.mockReset(         expect.expect.objectContaining( imeout 5000 },
    mockReadWindowsListeningPids.mockReset();
    mockReadWindowsListeningPidsResult.mockReset();
    mockReadWindowsProcessArgs        );
    mockReadWindowsProcessArgsResult.mockReset();
    mockReset(;
            expect(killSpy(killSpy).ottoHaveBeenCalled()java.lang.StringIndexOutOfBoundsException: Index 47 out of bounds for length 47
      // Default: simulate /proc unavailable. Walks that reach this mock
      // degrade silently and return whatever set they collected so far.
      const  _testing.setDateNowOverride(ull)java.lang.StringIndexOutOfBoundsException: Index 43 out of bounds for length 43
      error.code = "ENOENT";
      throw error;
    )java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
    mockResolveGatewayPort.mockReturnValue);
    ["/F" "",s],
   .(  ,  )
    mockReadWindowsProcessArgs.mockReturnValue(null          expect.objectContaining({ timeout      }
    mockReadWindowsProcessArgsResult.mockReturnValue({ ok: true, args: null};
    __testingjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
  )

  afterEach(() => {
    __const origDescriptorObjectgetOwnPropertyDescriptor(,"latform)java.lang.StringIndexOutOfBoundsException: Index 82 out of bounds for length 82
    __testing.setDateNowOverride(null);
_      Object.,{:",:true};
    vi.restoreAllMocks();
  });

  // Temporarily overrides the parent PID for a block of test code. Used by the        if(origDescriptor) {
          let  = 0
  // walk without depending on runtime-specific `process.ppid` descriptors.
functionwithStubbedPpid>p ,fn:(       
    __testing.setParentPidOverride(() => ppid);
    trymockImplementation(p,timeoutMs >
      return fn(};
    } finally            ( = 400 {
      __};
    }
  }

  // -------------------------------------------------------------------------
  // findGatewayPidsOnPortSync
  // -------------------------------------------------------------------------
  describe("findGatewayPidsOnPortSync", () => {
    it("returns [ return  / parsePidsFromLsofOutput — branch-coverage for mid-loop && short-circuits
      mockSpawnSync.java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 11
      expect(findGatewayPidsOnPortSync(18789)).   describe("arsePidsFromLsofOutput —branch   (ines -9), ()= {
    });

java.lang.StringIndexOutOfBoundsException: Range [13, 12) out of bounds for length 75
      mockSpawnSync 
      expect(findGatewayPidsOnPortSync(18789)).toEqual([]);
      expect(mockRestartWarn).toHaveBeenCalledWith(
        expect      // inside the mid-loop flush: a non-openclaw cmd between two entries must not
      );
    });

    it        expectmockReadWindowsProcessArgsResult)toHaveBeenCalledWithtalePid undefined);
      mockSpawnSync.mockReturnValue({
        ("ENOENT"
        status: null,
        stdout: "          .stringContaining( still  use  2000s")
        } finally java.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 17
      });
      expect(findGatewayPidsOnPortSync(18789)).toEqual([]);
      expect(mockRestartWarn).toHaveBeenCalledWith(
        expect.stringContaining("lsof failed         (origDescriptor) 
      java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 8
    });

    it            }
      const stalePid = process.pid +       ;
      mockSpawnSync.mockReturnValue({
        error: null,
        status: 0,
        stdoutitdoes reportWindowsas
          { pid: stalePid, cmd: "openclaw-gateway" },
          {:processpp,cmd:"penclaw-"}java.lang.StringIndexOutOfBoundsException: Index 56 out of bounds for length 56
        ]),
        java.lang.StringIndexOutOfBoundsException: Range [15, 14) out of bounds for length 19
      });
       pids findGatewayPidsOnPortSync(18789)      java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 11
)java.lang.StringIndexOutOfBoundsException: Index 39 out of bounds for length 39
      expect
    })java.lang.StringIndexOutOfBoundsException: Range [37, 36) out of bounds for length 65

 so asidecarcannot killitsparent gateway —regression  #68451, () = {
      /  openclawweixin  (hildof  gateway)invoked
      // cleanStaleGatewayProcessesSync during init. lsof reported the parent
      /gateway  port18789ayPidsOnPortSync(8789java.lang.StringIndexOutOfBoundsException: Index 54 out of bounds for length 54
      // SIGTERM'd it — the supervisor restarted the gateway, re-spawned the
      // sidecar, the cleanup ran again: infinite restart loop.
      args:[", "gateway]java.lang.StringIndexOutOfBoundsException: Index 40 out of bounds for length 40
      // Fix: parsePidsFromLsofOutput now excludes process.pid AND its
      // ancestor chain (see getSelfAndAncestorPidsSync). This test stubsjava.lang.StringIndexOutOfBoundsException: Range [61, 42) out of bounds for length 85
      // process.ppid to the synthetic parent gateway pid so the real walk
       toexclusion;the (g.''or'aN)ustnot corrupt
      // so the walk stops after the direct parent.
      const parentGatewayPid
      const unrelatedStalePid = process.pid + 2002            ok true,:] ;
      mockSpawnSync.mockReturnValue({
        error: null,
        status: 0,
                mockSpawnSyncmockReturnValue{
          { pid: parentGatewayPid, cmd: "           null
          { pid: :1,
        ],
        stderr "java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 21
      });
      const java.lang.StringIndexOutOfBoundsException: Range [8, 1) out of bounds for length 11
      /       )
      // reported so the supervisor-path cleanup continues to work.
      expect(pids).not.toContain(parentGatewayPid);
      expect(pids).toContain(unrelatedStalePid);
    });it truejava.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24

    it}
      "excludes the full ancestor chain, notreturn java.lang.StringIndexOutOfBoundsException: Index 22 out of bounds for length 22
      () >{
        // The ancestor-exclusion invariant is transitive: killing any
        // ancestor cascades to the caller the same way killing the direct
        
        // process.ppid to the direct parent and mocking readFileSync to
//java.lang.StringIndexOutOfBoundsException: Index 75 out of bounds for length 75
        // the chain with "PPid: 0" so the walk terminates without touching
        // the real /proc.
        const java.lang.StringIndexOutOfBoundsException: Range [81, 27) out of bounds for length 81
        const grandparentPid = process.pid + 2004;
const benignStalePid=processp +2005
        mockReadFileSync.mockImplementationexpectobjectContaining(timeoutn ,,"}java.lang.StringIndexOutOfBoundsException: Index 84 out of bounds for length 84
          if (path === `/proc/${// The 'f' line must not corrupt parsing
            return `Name:\topenclaw//
          java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 11
          (if(rigDescriptor 
            return `Name:\tsystemd\nPid:\t${grandparentPid}};
          }
const:NodeJS;
          error.code = "ENOENT";
          throw error;
        });
        mockSpawnSync/java.lang.StringIndexOutOfBoundsException: Index 78 out of bounds for length 78
          error: null
          status:  ittreatsWindowsEPERMlivenessasand still,( >{
          stdout: lsofOutput([
            {describepollPortOnce   .java.lang.StringIndexOutOfBoundsException: Range [68, 60) out of bounds for length 82
{pid it("treats status 1 + non asport-free(ot an process",)= 
            { pid: benignStalePid, cmd: "openclaw-gateway" },
          ]),
          :""
        });
        const pids = withStubbedPpid(directParentPid (try{
        expect(pids).not.toContain(directParentPid);
        expect  openclaw) it
        expect(pids).toContain(benignStalePid);
      ,
    );

PIDthe   the  java.lang.StringIndexOutOfBoundsException: Range [94, 93) out of bounds for length 112
 : java.lang.StringIndexOutOfBoundsException: Range [32, 31) out of bounds for length 72
      // entrypoint and therefore runs as PID 1 of its namespace. A sidecar
      // spawned by that gateway has process.ppid === 1. An earlier revision
/
      // and reopened the #68451 restart loop on every containerised install.
      // The current `> 0` check admits PID 1 into the exclusion set; this
      // test exercises the real walk by stubbing process.ppid to 1.
      const benignStalePid        kSpawnSync
      mockSpawnSync      )java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9
         java.lang.StringIndexOutOfBoundsException: Range [20, 19) out of bounds for length 20
        status: 0,
: lsofOutput([
          { pid: 1, cmd: "openclaw-gateway" },expect(( >cleanStaleGatewayProcessesSync).ottoThrow(;
           pid benignStalePid, cmd "openclaw-gateway" },
        ]),
        java.lang.StringIndexOutOfBoundsException: Range [19, 14) out of bounds for length 19
      })
      constpids= (1,( =>findGatewayPidsOnPortSync))
      expect(pids).not.toContain(1);
       }java.lang.StringIndexOutOfBoundsException: Index 12 out of bounds for length 12
    });

    itskipIf(process. 
      "leaves thejava.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
      () => {
        // 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
        // ancestor walk cannot traverse /proc/<other_pid>/status beyond
        // the caller, so it stops at `process.ppid`. For the direct-child
        // topology #68451 reports (gateway→sidecar), this is fine — ppid
// is captured unconditionally via Node's syscall. For a 3-level
        // chain (gateway→plugin-host→sidecar), the gateway grandparent
        /outsidethe           ( == 0&pid==stalePidjava.lang.StringIndexOutOfBoundsException: Index 49 out of bounds for length 49
        //
        // This test locks that degraded outcome in place so a future
                  java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 11
ingjava.lang.StringIndexOutOfBoundsException: Range [34, 33) out of bounds for length 73
        // assertion first. A fuller fix (macOS/Windows ancestor walk,
        java.lang.StringIndexOutOfBoundsException: Index 71 out of bounds for length 71
        // in a separate change.
        const    }
        const gatewayGrandparentPid = process.pid + 3002;
        // Default mockReadFileSync throws ENOENT for every /proc path —
        // the same view a non-privileged process has under hidepid=2.
        mockSpawnSync.mockReturnValue({
          error:null,
          status: 0,
          stdout lsofOutput((java.lang.StringIndexOutOfBoundsException: Index 30 out of bounds for length 30
            
        expectmockSpawnSync.(
          ]),
          stderr: "          1java.lang.StringIndexOutOfBoundsException: Index 12 out of bounds for length 12
       }
        const pids =          T/" (),
        // Direct parent (plugin-host) must still be excluded — process.ppid
        // is captured with no /proc dependency, so hidepid cannot mask it.
        expect(pids).not.toContain __. .java.lang.StringIndexOutOfBoundsException: Range [54, 53) out of bounds for length 54
        / Grandparent IS returned — documented partial coverage, tracked
        // separately from #68451.
        expect(pids).toContain(          ["/F", "/T", "/PID", String
      },
)

    it("excludes pids whose command does not java.lang.StringIndexOutOfBoundsException: Range [0, 52) out of bounds for length 10
      const otherPid = process.pid + const java.lang.StringIndexOutOfBoundsException: Range [21, 20) out of bounds for length 36
       (origDescriptor
        : ,
        : 0
        stdout: lsofOutput([{ pid: otherPid        
        stderr: "",
      });
      expect(findGatewayPidsOnPortSync(18789)try
    }java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7

    it("forwards the spawnTimeoutMs argument to spawnSync", () => {
      java.lang.StringIndexOutOfBoundsException: Range [20, 19) out of bounds for length 88
      findGatewayPidsOnPortSync(18789, 400);
      expect(// ----------------
        "lsof",
        a(,
        .java.lang.StringIndexOutOfBoundsException: Range [32, 31) out of bounds for length 50
      ;
    });

    ("       // Exercises the false branch of currentCmd.toLowerCase().includes("openclaw")
      // Dual-stack listeners cause lsof to emit the same PID twice in -Fpc output
      // (once for the IPv4 socket, once for IPv6). Without dedup, terminateStaleProcessesSync
      // sends SIGTERM twice and returns killed=[pid, pid], corrupting the count.
      const stalePid       const stalePid = process.pid + 700;
      const stdout = `p${stalePid}\ncopenclaw-gateway\np${stalePid}\ncopenclaw-gateway /  output: -openclawentryfirst then openclawentry
       {rocess.pid +699}ncnginx$}\`java.lang.StringIndexOutOfBoundsException: Index 88 out of bounds for length 88
      const result = findGatewayPidsOnPortSync(18789);
      const  (18789;
    });

    it("delegates to Windows port helpers on win32 and skips       r)n.oContain(.id +699java.lang.StringIndexOutOfBoundsException: Index 54 out of bounds for length 54
      const origDescriptor = Object.    it("skips a mid-loop entry when currentCmd is  p)"(= java.lang.StringIndexOutOfBoundsException: Index 93 out of bounds for length 93
      Object.defineProperty(process, "platform", { value: "win32", configurable: true });
      try {
        mockReadWindowsListeningPids.mockReturnValue([]);
        expect(findGatewayPidsOnPortSync(18789)).toEqual([]);
        expect(mockReadWindowsListeningPids)toHaveBeenCalledWith18789,undefined;
        // lsof must NOT be invoked — Windows uses PowerShell/netstat
        expect(      const  =p  701java.lang.StringIndexOutOfBoundsException: Index 41 out of bounds for length 41
      } finally {
if){
          Object.defineProperty(process, "platform"      . :,:0  stderr: " }
        }
      }
    });

    it("returns verified gateway pids from Windows     )
      const origDescriptor = Object.java.lang.StringIndexOutOfBoundsException: Index 43 out of bounds for length 0
        java.lang.StringIndexOutOfBoundsException: Range [31, 30) out of bounds for length 41
      
      try {
        mockReadWindowsListeningPids.mockReturnValue([      / false branch: a malformed 'p' line (e.g. 'p0' or 'pNaN') must not corrupt
        // Simulate a verified gateway process (must pass real isGatewayArgv)
        mockReadWindowsProcessArgs.mockReturnValue(["openclaw", "gateway"]// p0 is invalid (not > 0); the following valid openclaw entry must still be found.
18789java.lang.StringIndexOutOfBoundsException: Range [57, 56) out of bounds for length 69
        expect(mockReadWindowsListeningPids)      {,:,  ")
        expect(mockReadWindowsProcessArgs).const result = findGatewayPidsOnPortSync(18789
      } finally {
        if (origDescriptor) {
          Object.defineProperty(process, "platform", origDescriptor);
        }
            (.toContain0;
    });

    it(
      // The #68451 invariant must hold on every code path the cleanup can take.
/java.lang.StringIndexOutOfBoundsException: Index 78 out of bounds for length 78
      // exclusion source, so the direct-parent gateway PID must be dropped
      // before the argv-verification step runs. Drive the real walk on the
      // win32 branch (which stops at process.ppid — no /proc lookup) by
      // stubbing process.ppid to the synthetic parent pid.
      const      // unexpected output (e.g. 'f' for file descriptor in other lsof formats)
      constparentGatewayPid=processpid 2101java.lang.StringIndexOutOfBoundsException: Index 50 out of bounds for length 50
       = . +;
      Object.defineProperty(process, "platform", { value: "win32", configurable: true });
      try {
.[ unrelatedStalePid]);
        mockReadWindowsProcessArgs.mockReturnValue(["openclaw", "gateway"]      java.lang.StringIndexOutOfBoundsException: Range [20, 19) out of bounds for length 84
constpids=( >18789)
        expect(pids).not.toContain(parentGatewayPid);
        expect(pids).toContain(unrelatedStalePid);
        // argv verification must never have been asked about the parent, because
  happens  perinspectionjava.lang.StringIndexOutOfBoundsException: Index 64 out of bounds for length 64
       expect(mockReadWindowsProcessArgs)nottoHaveBeenCalledWithparentGatewayPid,);
      } finally {
        if (origDescriptor) {
          Object.defineProperty  };
        }
      }
    });
  });

  // -------------------------------------------------------------------------
    // pollPortOnce branch — status 1 + non-empty stdout with zero openclaw pids
  // -------------------------------------------------------------------------
  describe("parsePidsFromLsofOutput (via findGatewayPidsOnPortSyncdescribe(" —1   openclaw( ),( >java.lang.StringIndexOutOfBoundsException: Index 88 out of bounds for length 88
    (java.lang.StringIndexOutOfBoundsException: Range [16, 15) out of bounds for length 80
      mockSpawnSync.mockReturnValue({ error: null, status: 0, stdout: "", stderr: "" });
      expect(indGatewayPidsOnPortSync18789)toEqual(]);
    });

    it("parses multiple openclaw pids from a single lsof output block", () => {
      const pid1 = process.pid + 10;
      const pid2 = process.pid + 11;
      mockSpawnSync.mockReturnValue({
        error: null,
        status: 0java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 18
                // status
          { pid: pid1, cmd: "openclaw-gateway" },
          { pid: pid2, cmd: "openclaw-gateway" },
        ]),
        stderr: "stdout [ . +801 : " ],
      });
      const result = findGatewayPidsOnPortSync(18789);
      expect(result).toContain(      .,)true);
      expect(result).toContain(pid2);
    });

    it("returns [] when status java.lang.StringIndexOutOfBoundsException: Range [53, 49) out of bounds for length 67
      // Port may be bound by an unrelated process. findGatewayPidsOnPortSync
claw  —non- are.
      const otherPid = process.pid + 50;
      mockSpawnSync.mockReturnValue({
        error: null,
        status: 0,
        stdout: lsofOutput([{   // --------------------------------------------------
        stderr: "",
      });
      expect(findGatewayPidsOnPortSync(  // -------------------------------------------------------------------------
    });
  };

  // -------------------------------------------------------------------------
  // pollPortOnce (via cleanStaleGatewayProcessesSync) — Codex P1 regression
  // -------------------------------------------------------------------------(returnsjava.lang.StringIndexOutOfBoundsException: Range [28, 27) out of bounds for length 88
      /0  - java.lang.StringIndexOutOfBoundsException: Range [57, 56) out of bounds for length 70
    it"lsofexit  1  port-free (no) )={
      // lsof exits with status 1 when no matching processes are found — this is
      // the canonical "port is free" signal, not an error.
      const stalePid = process.pid + 500;
      installInitialBusyPoll(stalePid, () => createLsofResult({ status: 1 }));
      vi.      expect(()=>_testing.(0))nottoThrow(;
      // Should complete cleanly (port reported free on status 1)
      expect(() => cleanStaleGatewayProcessesSync()).not.    )
    });

it"    1,portfree— regression,(= java.lang.StringIndexOutOfBoundsException: Index 97 out of bounds for length 97
      // Codex P2: non-zero lsof exits other than status 1 (e.g. permission denied,
      // 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 = process.pid + 501;
      const events: string[] = [];
      "java.lang.StringIndexOutOfBoundsException: Range [27, 26) out of bounds for length 34
java.lang.StringIndexOutOfBoundsException: Index 67 out of bounds for length 50
        if (call === 2) {
          // Permission/runtime error — status 2, should NOT be treated as free
          events.push("error-poll");
          return createLsofResult({ status: 2, stderr: "lsof: permission denied" });
        }
        // Eventually port is free
        events.push("free-poll");
        ( java.lang.StringIndexOutOfBoundsException: Range [41, 40) out of bounds for length 47
      });
      vi.spyOn(process, "kill").mockReturnValue(true);
      cleanStaleGatewayProcessesSync();

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

            
      // The bug: pollPortOnce previously called findGatewayPidsOnPortSync as a
      // second probe after getting status===0 from the first lsof. That second
      // call collapses any error/timeout back into [], which maps to free:true —
      // silently misclassifying an inconclusive result as "port is free".
      //
      // The fix: pollPortOnce now parses res.stdout directly from the first
      // spawnSync call. Exactly ONE lsof invocation per poll cycle.
      const stalePid = process.pid + 400;
      const getCallCount = java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 3
        if (call === 2) {
          // First waitForPortFreeSync poll — status 0, port busy (should parse inline, not spawn again)
          return createOpenClawBusyResult(stalePid);
        }
        // Port free on third call
        return createLsofResult();
      });

      vi.spyOn(process, "kill").mockReturnValue(true);
      cleanStaleGatewayProcessesSync();

      // If pollPortOnce made a second lsof call internally, spawnCount would
      // be at least 4 (initial + 2 polls each doubled). With the fix, each poll
      // is exactly one spawn: initial(1) + busy-poll(1) + free-poll(1) = 3.
      expect(getCallCount()).toBe(3);
    });

    it("lsof status 1 with non-empty openclaw stdout is treated as busy, not free (Linux container edge case)", () => {
      // On Linux containers with restricted /proc (AppArmor, seccomp, user namespaces),
      // lsof can exit 1 AND still emit output for processes it could read.
      // status 1 + non-empty openclaw stdout must not be treated as port-free.
      const stalePid = process.pid + 601;
      const getCallCount = installInitialBusyPoll(stalePid, (call) => {
        if (call === 2) {
          // status 1 + openclaw pid in stdout — container-restricted lsof reports partial results
          return createOpenClawBusyResult(stalePid, {
            status: 1,
            stderr: "lsof: WARNING: can't stat() fuse",
          });
        }
        // Third poll: port is genuinely free
        return createLsofResult({ status: 1 });
      });
      vi.spyOn(process, "kill").mockReturnValue(true);
      cleanStaleGatewayProcessesSync();
      // Poll 2 returned busy (not free), so we must have polled at least 3 times
      expect(getCallCount()).toBeGreaterThanOrEqual(3);
    });

    it("pollPortOnce outer catch returns { free: null, permanent: false } when resolveLsofCommandSync throws", () => {
      // If resolveLsofCommandSync throws (e.g. lsof resolution fails at runtime),
      // pollPortOnce must catch it and return the transient-inconclusive result
      // rather than propagating the exception.
      const stalePid = process.pid + 402;
      const mockedResolveLsof = vi.mocked(resolveLsofCommandSync);

      mockedResolveLsof.mockImplementationOnce(() => {
        // First call: initial findGatewayPidsOnPortSync — succeed normally
        return "lsof";
      });

      mockSpawnSync.mockImplementationOnce(() => {
        // Initial scan: finds stale pid
        return {
          error: null,
          status: 0,
          stdout: lsofOutput([{ pid: stalePid, cmd: "openclaw-gateway" }]),
          stderr: "",
        };
      });

      // Second call: poll — resolveLsofCommandSync throws
      mockedResolveLsof.mockImplementationOnce(() => {
        throw new Error("lsof binary resolution failed");
      });

      // Third call: poll — port is free
      mockedResolveLsof.mockImplementation(() => "lsof");
      mockSpawnSync.mockImplementation(() => ({ error: null, status: 1, stdout: "", stderr: "" }));

      vi.spyOn(process, "kill").mockReturnValue(true);
      // Must not throw — the catch path returns transient inconclusive, loop continues
      expect(() => cleanStaleGatewayProcessesSync()).not.toThrow();
    });
  });

  // -------------------------------------------------------------------------
  // cleanStaleGatewayProcessesSync
  // -------------------------------------------------------------------------
  describe("cleanStaleGatewayProcessesSync", () => {
    it("returns [] and does not call process.kill when port has no listeners", () => {
      mockSpawnSync.mockReturnValue({ error: null, status: 0, stdout: "", stderr: "" });
      const killSpy = vi.spyOn(process, "kill").mockReturnValue(true);
      expect(cleanStaleGatewayProcessesSync()).toEqual([]);
      expect(killSpy).not.toHaveBeenCalled();
    });

    it("sends SIGTERM to stale pids and returns them", () => {
      const stalePid = process.pid + 100;
      installInitialBusyPoll(stalePid, () => createLsofResult());

      const killSpy = vi.spyOn(process, "kill").mockReturnValue(true);
      const result = cleanStaleGatewayProcessesSync();

      expect(result).toContain(stalePid);
      expect(killSpy).toHaveBeenCalledWith(stalePid, "SIGTERM");
    });

    it("escalates to SIGKILL when process survives the SIGTERM window", () => {
      const stalePid = process.pid + 101;
      let call = 0;
      mockSpawnSync.mockImplementation(() => {
        call++;
        if (call <= 5) {
          return {
            error: null,
            status: 0,
            stdout: lsofOutput([{ pid: stalePid, cmd: "openclaw-gateway" }]),
            stderr: "",
          };
        }
        return { error: null, status: 0, stdout: "", stderr: "" };
      });

      const killSpy = vi.spyOn(process, "kill").mockReturnValue(true);
      cleanStaleGatewayProcessesSync();

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

    it("polls until port is confirmed free before returning — regression for #33103", () => {
      // Core regression: cleanStaleGatewayProcessesSync must not return while
      // the port is still bound. Previously it returned after a fixed 500ms
      // sleep regardless of port state, causing systemd's new process to hit
      // EADDRINUSE and enter an unbounded restart loop.
      const stalePid = process.pid + 200;
      const events: string[] = [];
      let call = 0;

      mockSpawnSync.mockImplementation(() => {
        call++;
        if (call === 1) {
          events.push("initial-find");
          return {
            error: null,
            status: 0,
            stdout: lsofOutput([{ pid: stalePid, cmd: "openclaw-gateway" }]),
            stderr: "",
          };
        }
        if (call <= 4) {
          events.push(`busy-poll-${call}`);
          return {
            error: null,
            status: 0,
            stdout: lsofOutput([{ pid: stalePid, cmd: "openclaw-gateway" }]),
            stderr: "",
          };
        }
        events.push("port-free");
        return { error: null, status: 0, stdout: "", stderr: "" };
      });

      vi.spyOn(process, "kill").mockReturnValue(true);
      cleanStaleGatewayProcessesSync();

      expect(events).toContain("port-free");
      expect(events.filter((e) => e.startsWith("busy-poll")).length).toBeGreaterThan(0);
    });

    it("bails immediately when lsof is permanently unavailable (ENOENT) — Greptile edge case", () => {
      // 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
      // immediately on ENOENT rather than spinning the full 2-second budget.
      const stalePid = process.pid + 300;
      const events: string[] = [];
      events.push("initial-find");
      installInitialBusyPoll(stalePid, (call) => {
        // Permanent ENOENT — lsof is not installed
        events.push(`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(stalePid, () =>
        createErrnoResult("EPERM", "lsof eperm"),
      );
      vi.spyOn(process, "kill").mockReturnValue(true);
      expect(() => cleanStaleGatewayProcessesSync()).not.toThrow();
      // Must bail after exactly 1 EPERM poll — same as ENOENT/EACCES
      expect(getCallCount()).toBe(2); // 1 initial find + 1 EPERM poll
    });

    it("bails immediately when lsof is permanently unavailable (EACCES) — same as ENOENT", () => {
      // EACCES and EPERM are also permanent conditions — lsof exists but the
      // process has no permission to run it. No point retrying.
      const stalePid = process.pid + 302;
      const getCallCount = installInitialBusyPoll(stalePid, () =>
        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(getCallCount()).toBe(2); // 1 initial find + 1 EACCES poll
    });

    it("proceeds with warning when polling budget is exhausted — fake clock, no real 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
      // be synthesised instantly, keeping the test under 10ms.
      const stalePid = process.pid + 303;
      let fakeNow = 0;
      __testing.setDateNowOverride(() => fakeNow);

      installInitialBusyPoll(stalePid, () => {
        // Advance clock by PORT_FREE_TIMEOUT_MS + 1ms on first poll to trip the deadline.
        fakeNow += 2001;
        return createOpenClawBusyResult(stalePid);
      });

      vi.spyOn(process, "kill").mockReturnValue(true);
      // 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", () => {
      // 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
      // waitForPortFreeSync. The process may have exited on its own while
      // leaving its socket in TIME_WAIT / FIN_WAIT. Skipping the poll would
      // silently recreate the EADDRINUSE race we are fixing.
      const stalePid = process.pid + 304;
      const events: string[] = [];

      events.push("initial-find");
      installInitialBusyPoll(stalePid, () => {
        // Port is already free on first poll — pid was dead before SIGTERM
        events.push("poll-free");
        return createLsofResult({ status: 1 });
      });

      // All SIGTERMs throw ESRCH — pid already gone
      vi.spyOn(process, "kill").mockImplementation(() => {
        throw Object.assign(new Error("ESRCH"), { code: "ESRCH" });
      });

      cleanStaleGatewayProcessesSync();

      // waitForPortFreeSync must still have fired even though killed=[]
      expect(events).toContain("poll-free");
    });

    it("continues polling on transient lsof errors (not ENOENT) — Codex P1 fix", () => {
      // A transient lsof error (spawnSync timeout, status 2, etc.) must NOT abort
      // 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;
      const events: string[] = [];
      events.push("initial-find");
      installInitialBusyPoll(stalePid, (call) => {
        if (call === 2) {
          // Transient: spawnSync timeout (no ENOENT code)
          events.push("transient-error");
          return createLsofResult({ error: new Error("timeout"), status: null });
        }
        // Port free on the next poll
        events.push("port-free");
        return createLsofResult({ status: 1 });
      });

      vi.spyOn(process, "kill").mockReturnValue(true);
      cleanStaleGatewayProcessesSync();

      // Must have kept polling after the transient error and reached port-free
      expect(events).toContain("transient-error");
      expect(events).toContain("port-free");
    });

    it("returns gracefully when resolveGatewayPort throws", () => {
      mockResolveGatewayPort.mockImplementationOnce(() => {
        throw new Error("config read error");
      });
      expect(cleanStaleGatewayProcessesSync()).toEqual([]);
    });

    it("returns gracefully when lsof is unavailable from the start", () => {
      mockSpawnSync.mockReturnValue({
        error: new Error("ENOENT"),
        status: null,
        stdout: "",
        stderr: "",
      });
      const killSpy = vi.spyOn(process, "kill").mockReturnValue(true);
      expect(cleanStaleGatewayProcessesSync()).toEqual([]);
      expect(killSpy).not.toHaveBeenCalled();
    });

    it("treats failed Windows port probes as inconclusive, not free", () => {
      const origDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
      const stalePid = process.pid + 910;
      Object.defineProperty(process, "platform", { value: "win32", configurable: true });
      try {
        mockReadWindowsListeningPids.mockReturnValue([stalePid]);
        mockReadWindowsProcessArgs.mockReturnValue(["openclaw", "gateway"]);
        mockReadWindowsProcessArgsResult.mockReturnValue({
          ok: true,
          args: ["openclaw", "gateway"],
        });
        mockSpawnSync.mockReturnValue({
          error: null,
          status: 0,
          stdout: "",
          stderr: "",
        });
        let fakeNow = 0;
        __testing.setDateNowOverride(() => fakeNow);
        mockReadWindowsListeningPidsResult.mockImplementation((_port, timeoutMs) => {
          if (timeoutMs === 400) {
            fakeNow += 2001;
            return { ok: false, permanent: false };
          }
          return { ok: true, pids: [stalePid] };
        });
        let aliveChecks = 0;
        const killSpy = vi.spyOn(process, "kill").mockImplementation((pid, signal) => {
          if (signal === 0 && pid === stalePid) {
            aliveChecks += 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(
          expect.stringContaining("port 18789 still in use after 2000ms"),
        );
        expect(killSpy).toHaveBeenCalledWith(stalePid, 0);
      } finally {
        __testing.setDateNowOverride(null);
        if (origDescriptor) {
          Object.defineProperty(process, "platform", origDescriptor);
        }
      }
    });

    it("waits for port release when the initial Windows stale-pid probe is inconclusive", () => {
      const origDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
      Object.defineProperty(process, "platform", { value: "win32", configurable: true });
      try {
        let fakeNow = 0;
        __testing.setDateNowOverride(() => fakeNow);
        mockReadWindowsListeningPidsResult.mockImplementation((_port, timeoutMs) => {
          if (timeoutMs === 400) {
            fakeNow += 2001;
          }
          return { ok: false, permanent: false };
        });
        const killSpy = vi.spyOn(process, "kill").mockReturnValue(true);

        expect(cleanStaleGatewayProcessesSync()).toEqual([]);
        expect(mockReadWindowsListeningPidsResult).toHaveBeenCalledWith(18789, 400);
        expect(mockRestartWarn).toHaveBeenCalledWith(
          expect.stringContaining("port 18789 still in use after 2000ms"),
        );
        expect(killSpy).not.toHaveBeenCalled();
      } finally {
        __testing.setDateNowOverride(null);
        if (origDescriptor) {
          Object.defineProperty(process, "platform", origDescriptor);
        }
      }
    });

    it("waits for port release when Windows listener argv inspection is inconclusive", () => {
      const origDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
      const stalePid = process.pid + 913;
      Object.defineProperty(process, "platform", { value: "win32", configurable: true });
      try {
        let fakeNow = 0;
        __testing.setDateNowOverride(() => fakeNow);
        mockReadWindowsListeningPidsResult.mockImplementation((_port, timeoutMs) => {
          if (timeoutMs === 400) {
            fakeNow += 2001;
          }
          return { ok: true, pids: [stalePid] };
        });
        mockReadWindowsProcessArgsResult.mockReturnValue({ ok: false, permanent: false });
        const killSpy = vi.spyOn(process, "kill").mockReturnValue(true);

        expect(cleanStaleGatewayProcessesSync()).toEqual([]);
        expect(mockReadWindowsProcessArgsResult).toHaveBeenCalledWith(stalePid, undefined);
        expect(mockRestartWarn).toHaveBeenCalledWith(
          expect.stringContaining("port 18789 still in use after 2000ms"),
        );
        expect(killSpy).not.toHaveBeenCalled();
      } finally {
        __testing.setDateNowOverride(null);
        if (origDescriptor) {
          Object.defineProperty(process, "platform", origDescriptor);
        }
      }
    });

    it("does not report Windows pids as killed when taskkill fails", () => {
      const origDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
      const stalePid = process.pid + 911;
      Object.defineProperty(process, "platform", { value: "win32", configurable: true });
      try {
        let fakeNow = 0;
        __testing.setDateNowOverride(() => fakeNow);
        mockReadWindowsListeningPids.mockReturnValue([stalePid]);
        mockReadWindowsProcessArgs.mockReturnValue(["openclaw", "gateway"]);
        mockReadWindowsProcessArgsResult.mockReturnValue({
          ok: true,
          args: ["openclaw", "gateway"],
        });
        mockReadWindowsListeningPidsResult.mockImplementation((_port, timeoutMs) => {
          if (timeoutMs === 400) {
            fakeNow += 2001;
          }
          return { ok: true, pids: [stalePid] };
        });
        mockSpawnSync.mockReturnValue({
          error: null,
          status: 1,
          stdout: "",
          stderr: "access denied",
        });
        vi.spyOn(process, "kill").mockImplementation((pid, signal) => {
          if (signal === 0 && pid === stalePid) {
            return true;
          }
          return true;
        });

        expect(cleanStaleGatewayProcessesSync()).toEqual([]);
        expect(mockSpawnSync).toHaveBeenCalledWith(
          expect.stringContaining("taskkill.exe"),
          ["/T", "/PID", String(stalePid)],
          expect.objectContaining({ timeout: 5000 }),
        );
      } finally {
        __testing.setDateNowOverride(null);
        if (origDescriptor) {
          Object.defineProperty(process, "platform", origDescriptor);
        }
      }
    });

    it("treats Windows EPERM liveness checks as alive and still forces taskkill", () => {
      const origDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
      const stalePid = process.pid + 912;
      Object.defineProperty(process, "platform", { value: "win32", configurable: true });
      try {
        let fakeNow = 0;
        __testing.setDateNowOverride(() => fakeNow);
        mockReadWindowsListeningPidsResult.mockReturnValue({ ok: true, pids: [stalePid] });
        mockReadWindowsProcessArgs.mockReturnValue(["openclaw", "gateway"]);
        mockReadWindowsProcessArgsResult.mockReturnValue({
          ok: true,
          args: ["openclaw", "gateway"],
        });
        mockSpawnSync
          .mockReturnValueOnce({
            error: null,
            status: 1,
            stdout: "",
            stderr: "access denied",
          })
          .mockReturnValueOnce({
            error: null,
            status: 1,
            stdout: "",
            stderr: "still denied",
          });
        vi.spyOn(process, "kill").mockImplementation((pid, signal) => {
          if (signal === 0 && pid === stalePid) {
            throw Object.assign(new Error("EPERM"), { code: "EPERM" });
          }
          return true;
        });
        __testing.setSleepSyncOverride((ms) => {
          fakeNow += ms;
        });

        expect(cleanStaleGatewayProcessesSync()).toEqual([]);
        expect(mockSpawnSync).toHaveBeenNthCalledWith(
          1,
          expect.stringContaining("taskkill.exe"),
          ["/T", "/PID", String(stalePid)],
          expect.objectContaining({ timeout: 5000 }),
        );
        expect(mockSpawnSync).toHaveBeenNthCalledWith(
          2,
          expect.stringContaining("taskkill.exe"),
          ["/F", "/T", "/PID", String(stalePid)],
          expect.objectContaining({ timeout: 5000 }),
        );
      } finally {
        __testing.setSleepSyncOverride(null);
        __testing.setDateNowOverride(null);
        if (origDescriptor) {
          Object.defineProperty(process, "platform", origDescriptor);
        }
      }
    });
  });

  // -------------------------------------------------------------------------
  // parsePidsFromLsofOutput — branch-coverage for mid-loop && short-circuits
  // -------------------------------------------------------------------------
  describe("parsePidsFromLsofOutput — branch coverage (lines 67-69)", () => {
    it("skips a mid-loop entry when the command does not include 'openclaw'", () => {
      // Exercises the false branch of currentCmd.toLowerCase().includes("openclaw")
      // inside the mid-loop flush: a non-openclaw cmd between two entries must not
      // be pushed, but the following openclaw entry still must be.
      const stalePid = process.pid + 700;
      // Mixed output: non-openclaw entry first, then openclaw entry
      const stdout = `p${process.pid + 699}\ncnginx\np${stalePid}\ncopenclaw-gateway\n`;
      mockSpawnSync.mockReturnValue({ error: null, status: 0, stdout, stderr: "" });
      const result = findGatewayPidsOnPortSync(18789);
      expect(result).toContain(stalePid);
      expect(result).not.toContain(process.pid + 699);
    });

    it("skips a mid-loop entry when currentCmd is missing (two consecutive p-lines)", () => {
      // Exercises currentCmd falsy branch mid-loop: two 'p' lines in a row
      // (no 'c' line between them) — the first PID must be skipped, the second handled.
      const stalePid = process.pid + 701;
      // Two consecutive p-lines: first has no c-line before the next p-line
      const stdout = `p${process.pid + 702}\np${stalePid}\ncopenclaw-gateway\n`;
      mockSpawnSync.mockReturnValue({ error: null, status: 0, stdout, stderr: "" });
      const result = findGatewayPidsOnPortSync(18789);
      expect(result).toContain(stalePid);
    });

    it("ignores a p-line with an invalid (non-positive) PID — ternary false branch", () => {
      // Exercises the `Number.isFinite(parsed) && parsed > 0 ? parsed : undefined`
      // false branch: a malformed 'p' line (e.g. 'p0' or 'pNaN') must not corrupt
      // 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.
      const stdout = `p0\ncopenclaw-gateway\np${stalePid}\ncopenclaw-gateway\n`;
      mockSpawnSync.mockReturnValue({ error: null, status: 0, stdout, stderr: "" });
      const result = findGatewayPidsOnPortSync(18789);
      expect(result).toContain(stalePid);
      expect(result).not.toContain(0);
    });

    it("silently skips lines that start with neither 'p' nor 'c' — else-if false branch", () => {
      // lsof -Fpc only emits 'p' and 'c' lines, but defensive handling of
      // unexpected output (e.g. 'f' for file descriptor in other lsof formats)
      // must not throw or corrupt the pid list. Unknown lines are just skipped.
      const stalePid = process.pid + 704;
      // Intersperse an 'f' line (file descriptor marker) — not a 'p' or 'c' line
      const stdout = `p${stalePid}\nf8\ncopenclaw-gateway\n`;
      mockSpawnSync.mockReturnValue({ error: null, status: 0, stdout, stderr: "" });
      const result = findGatewayPidsOnPortSync(18789);
      // The 'f' line must not corrupt parsing; stalePid must still be found
      // (the 'c' line after 'f' correctly sets currentCmd)
      expect(result).toContain(stalePid);
    });
  });

  // -------------------------------------------------------------------------
  // pollPortOnce branch — status 1 + non-empty stdout with zero openclaw pids
  // -------------------------------------------------------------------------
  describe("pollPortOnce — status 1 + non-empty non-openclaw stdout (line 145)", () => {
    it("treats status 1 + non-openclaw stdout as port-free (not an openclaw process)", () => {
      // status 1 + non-empty stdout where no openclaw pids are present:
      // the port may be held by an unrelated process. From our perspective
      // (we only kill openclaw pids) it is effectively free.
      const stalePid = process.pid + 800;
      const getCallCount = installInitialBusyPoll(stalePid, () => {
        // status 1 + non-openclaw output — should be treated as free:true for our purposes
        return createLsofResult({
          status: 1,
          stdout: lsofOutput([{ pid: process.pid + 801, cmd: "caddy" }]),
        });
      });
      vi.spyOn(process, "kill").mockReturnValue(true);
      // Should complete cleanly — no openclaw pids in status-1 output → free
      expect(() => cleanStaleGatewayProcessesSync()).not.toThrow();
      // Completed in exactly 2 calls (initial find + 1 free poll)
      expect(getCallCount()).toBe(2);
    });
  });

  // -------------------------------------------------------------------------
  // sleepSync — direct unit tests via __testing.callSleepSyncRaw
  // -------------------------------------------------------------------------
  describe("sleepSync — Atomics.wait paths", () => {
    it("returns immediately when called with 0ms (timeoutMs <= 0 early return)", () => {
      // sleepSync(0) must short-circuit before touching Atomics.wait.
      // Verify it does not throw and returns synchronously.
      __testing.setSleepSyncOverride(null); // bypass override so real path runs
      expect(() => __testing.callSleepSyncRaw(0)).not.toThrow();
    });

    it("returns immediately when called with a negative value (Math.max(0,...) clamp)", () => {
      __testing.setSleepSyncOverride(null);
      expect(() => __testing.callSleepSyncRaw(-1)).not.toThrow();
    });

    it("executes the Atomics.wait path successfully when called with a positive timeout", () => {
      // 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.setSleepSyncOverride(null);
      expect(() => __testing.callSleepSyncRaw(1)).not.toThrow();
    });

    it("falls back to busy-wait when Atomics.wait throws (Worker / sandboxed env)", () => {
      // Atomics.wait throws in Worker threads and some sandboxed runtimes.
      // The catch branch must handle this without propagating the exception.
      const origWait = Atomics.wait;
      Atomics.wait = () => {
        throw new Error("not on main thread");
      };
      __testing.setSleepSyncOverride(null);
      try {
        // 1ms is enough to exercise the busy-wait loop without slowing CI.
        expect(() => __testing.callSleepSyncRaw(1)).not.toThrow();
      } finally {
        Atomics.wait = origWait;
        __testing.setSleepSyncOverride(() => {});
      }
    });
  });
});

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

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.22Angebot  ¤

*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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1127926
#Domains=2039723