@@ -4570,3 +4570,89 @@ test "issue-116: getGitHead returns valid SHA for git repos" {
45704570 }
45714571 }
45724572}
4573+
4574+ test "issue-148: idle timeout is 10 minutes" {
4575+ const mcp = @import ("mcp.zig" );
4576+ try testing .expectEqual (@as (i64 , 10 * 60 * 1000 ), mcp .idle_timeout_ms );
4577+ }
4578+
4579+ test "issue-148: POLLHUP detects closed pipe" {
4580+ const pipe = try std .posix .pipe ();
4581+ std .posix .close (pipe [1 ]);
4582+
4583+ var poll_fds = [_ ]std.posix.pollfd {.{
4584+ .fd = pipe [0 ],
4585+ .events = std .posix .POLL .IN | std .posix .POLL .HUP ,
4586+ .revents = 0 ,
4587+ }};
4588+
4589+ const result = try std .posix .poll (& poll_fds , 0 );
4590+ try testing .expect (result > 0 );
4591+ try testing .expect ((poll_fds [0 ].revents & std .posix .POLL .HUP ) != 0 );
4592+ std .posix .close (pipe [0 ]);
4593+ }
4594+
4595+ test "issue-148: open pipe does not trigger HUP" {
4596+ const pipe = try std .posix .pipe ();
4597+ defer std .posix .close (pipe [0 ]);
4598+ defer std .posix .close (pipe [1 ]);
4599+
4600+ var poll_fds = [_ ]std.posix.pollfd {.{
4601+ .fd = pipe [0 ],
4602+ .events = std .posix .POLL .IN | std .posix .POLL .HUP ,
4603+ .revents = 0 ,
4604+ }};
4605+
4606+ const result = try std .posix .poll (& poll_fds , 0 );
4607+ try testing .expectEqual (@as (usize , 0 ), result );
4608+ }
4609+
4610+ test "issue-148: codedb mcp exits when stdin is closed" {
4611+ // Integration test: spawn codedb mcp, close stdin, verify it exits
4612+ var child = std .process .Child .init (
4613+ &.{ "zig" , "build" , "run" , "--" , "--mcp" },
4614+ testing .allocator ,
4615+ );
4616+ child .stdin_behavior = .Pipe ;
4617+ child .stdout_behavior = .Pipe ;
4618+ child .stderr_behavior = .Ignore ;
4619+
4620+ try child .spawn ();
4621+
4622+ // Send initialize then close stdin (simulate client crash)
4623+ const init_msg = "{\" jsonrpc\" :\" 2.0\" ,\" id\" :1,\" method\" :\" initialize\" ,\" params\" :{\" protocolVersion\" :\" 2024-11-05\" ,\" capabilities\" :{},\" clientInfo\" :{\" name\" :\" test\" ,\" version\" :\" 1\" }}}" ;
4624+ const header = std .fmt .comptimePrint ("Content-Length: {d}\r \n \r \n " , .{init_msg .len });
4625+
4626+ if (child .stdin ) | stdin | {
4627+ stdin .writeAll (header ) catch {};
4628+ stdin .writeAll (init_msg ) catch {};
4629+ // Close stdin — simulates client disconnecting
4630+ stdin .close ();
4631+ child .stdin = null ;
4632+ }
4633+
4634+ // Wait up to 15 seconds for the process to exit
4635+ // (watchdog polls every 10s, so it should detect POLLHUP within ~10s)
4636+ const start = std .time .milliTimestamp ();
4637+ const term = child .wait () catch {
4638+ // If wait fails, the process is stuck — test fails
4639+ try testing .expect (false );
4640+ return ;
4641+ };
4642+
4643+ const elapsed = std .time .milliTimestamp () - start ;
4644+
4645+ // Should have exited (not been killed by us)
4646+ switch (term ) {
4647+ .Exited = > | code | {
4648+ // Any exit code is fine — we just care that it exited
4649+ _ = code ;
4650+ },
4651+ else = > {
4652+ // Signal-killed or other — acceptable
4653+ },
4654+ }
4655+
4656+ // Should exit within 15 seconds (10s poll interval + margin)
4657+ try testing .expect (elapsed < 15_000 );
4658+ }
0 commit comments