/**
* Regression: overlay-mirror git-subprocess stdio fd leak (2026-07-13).
*
* `_runOverlayMirror`'s git() helper spawned a child, read stdout/stderr, and
* resolved on 'exit' WITHOUT closing the parent-side stdio streams. bare-subprocess
* backs child stdio with unix socketpairs and does NOT auto-close them on 'exit',
* so each spawn leaked ~1 fd. The mirror runs ~one git per repo/pointer every 5
* min (~hundreds of spawns), leaking ~89 fds/min → the always-on sidecar climbed
* to 232k fds / 15.8 GB in 43h on stortron. These are anonymous socketpairs, so
* the hyper-svc UdsServer idle reaper never reclaims them.
*
* spawnCaptureNoLeak (the shared helper git() now delegates to) listens on 'close'
* and destroys the stdio streams. This test proves it does not leak fds under a
* burst of spawns.
*/
import test from 'brittle'
import fs from '#node:fs'
import process from '#node:process'
import { spawn } from '#node:child_process'
import { spawnCaptureNoLeak } from '../lib/index.js'
const fdCount = () => fs.readdirSync('/proc/' + process.pid + '/fd').length
test('spawnCaptureNoLeak does not leak parent-side stdio fds across many spawns', async (t) => {
const N = 50
// Warm up once so any one-time module/handle allocation is excluded.
await spawnCaptureNoLeak(spawn, 'git', ['--version'])
await new Promise((r) => setTimeout(r, 200))
const before = fdCount()
for (let i = 0; i < N; i++) {
const out = await spawnCaptureNoLeak(spawn, 'git', ['--version'])
t.ok(out.includes('git version'), 'stdout captured (spawn ' + i + ')')
}
await new Promise((r) => setTimeout(r, 300))
const after = fdCount()
const perSpawn = (after - before) / N
t.comment('fd delta over ' + N + ' spawns: ' + (after - before) + ' (~' + perSpawn.toFixed(2) + '/spawn)')
// The buggy pattern leaked ~1 fd/spawn (→ ~50 here). Allow a tiny slack for
// unrelated runtime allocations but fail well below the leak signature.
t.ok(after - before <= 5, 'fd growth stays bounded (no ~1/spawn socketpair leak)')
})
test('spawnCaptureNoLeak rejects on non-zero exit with stderr', async (t) => {
await t.exception(
spawnCaptureNoLeak(spawn, 'git', ['rev-parse', '--verify', 'definitely-not-a-ref-xyz']),
/./,
'non-zero git exit rejects'
)
})