Skip to content

Commit ae73412

Browse files
mksgluclaude
andcommitted
test(lifecycle): add regression guards for stdin listener removal (#236)
Two new unit tests on top of PR #255 cherry-pick: - Verify lifecycle guard does NOT attach stdin listeners (count check) - Verify process.stdin.resume() is never called (spy check) These act as regression guards — will break if stdin handling is accidentally re-introduced. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 156a1cd commit ae73412

1 file changed

Lines changed: 61 additions & 0 deletions

File tree

tests/lifecycle.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,67 @@ describe("Lifecycle Guard", () => {
107107
assert.equal(shutdownCalled, false);
108108
});
109109

110+
test("cleanup does NOT remove stdin listeners (stdin is not used)", async () => {
111+
// Capture listeners before guard starts
112+
const stdinListenersBefore = process.stdin.listenerCount("close")
113+
+ process.stdin.listenerCount("end")
114+
+ process.stdin.listenerCount("data")
115+
+ process.stdin.listenerCount("error")
116+
+ process.stdin.listenerCount("readable");
117+
118+
const cleanup = startLifecycleGuard({
119+
checkIntervalMs: 50,
120+
onShutdown: () => {},
121+
isParentAlive: () => true,
122+
});
123+
124+
// Capture listeners after guard starts
125+
const stdinListenersAfterStart = process.stdin.listenerCount("close")
126+
+ process.stdin.listenerCount("end")
127+
+ process.stdin.listenerCount("data")
128+
+ process.stdin.listenerCount("error")
129+
+ process.stdin.listenerCount("readable");
130+
131+
cleanup();
132+
133+
// Capture listeners after cleanup
134+
const stdinListenersAfterCleanup = process.stdin.listenerCount("close")
135+
+ process.stdin.listenerCount("end")
136+
+ process.stdin.listenerCount("data")
137+
+ process.stdin.listenerCount("error")
138+
+ process.stdin.listenerCount("readable");
139+
140+
// Guard should not have added any stdin listeners
141+
assert.equal(stdinListenersAfterStart, stdinListenersBefore,
142+
"startLifecycleGuard must not add stdin listeners");
143+
// Cleanup should not have removed any stdin listeners
144+
assert.equal(stdinListenersAfterCleanup, stdinListenersBefore,
145+
"cleanup must not remove stdin listeners");
146+
});
147+
148+
test("startLifecycleGuard does NOT call process.stdin.resume()", async () => {
149+
let resumeCalled = false;
150+
const originalResume = process.stdin.resume.bind(process.stdin);
151+
process.stdin.resume = (() => { resumeCalled = true; return originalResume(); }) as typeof process.stdin.resume;
152+
153+
try {
154+
const cleanup = startLifecycleGuard({
155+
checkIntervalMs: 50,
156+
onShutdown: () => {},
157+
isParentAlive: () => true,
158+
});
159+
160+
// Give one tick to ensure any async resume would have fired
161+
await new Promise((r) => setTimeout(r, 100));
162+
163+
cleanup();
164+
assert.equal(resumeCalled, false, "process.stdin.resume() must not be called by lifecycle guard");
165+
} finally {
166+
// Restore original resume to avoid polluting other tests
167+
process.stdin.resume = originalResume;
168+
}
169+
});
170+
110171
test("detects ppid=0 as dead parent (Windows behavior)", async () => {
111172
let shutdownCalled = false;
112173

0 commit comments

Comments
 (0)