11import io
22import sys
3+ import tempfile
34from io import TextIOWrapper
45
56import anyio
@@ -73,12 +74,15 @@ async def test_stdio_server_invalid_utf8(monkeypatch: pytest.MonkeyPatch):
7374 """
7475 # \xff\xfe are invalid UTF-8 start bytes.
7576 valid = JSONRPCRequest (jsonrpc = "2.0" , id = 1 , method = "ping" )
76- raw_stdin = io .BytesIO (b"\xff \xfe \n " + valid .model_dump_json (by_alias = True , exclude_none = True ).encode () + b"\n " )
77+ raw_stdin = tempfile .TemporaryFile ("w+b" )
78+ raw_stdin .write (b"\xff \xfe \n " + valid .model_dump_json (by_alias = True , exclude_none = True ).encode () + b"\n " )
79+ raw_stdin .seek (0 )
80+ raw_stdout = tempfile .TemporaryFile ("w+b" )
7781
7882 # Replace sys.stdin with a wrapper whose .buffer is our raw bytes, so that
7983 # stdio_server()'s default path wraps it with errors='replace'.
8084 monkeypatch .setattr (sys , "stdin" , TextIOWrapper (raw_stdin , encoding = "utf-8" ))
81- monkeypatch .setattr (sys , "stdout" , TextIOWrapper (io . BytesIO () , encoding = "utf-8" ))
85+ monkeypatch .setattr (sys , "stdout" , TextIOWrapper (raw_stdout , encoding = "utf-8" ))
8286
8387 with anyio .fail_after (5 ):
8488 async with stdio_server () as (read_stream , write_stream ):
@@ -92,3 +96,38 @@ async def test_stdio_server_invalid_utf8(monkeypatch: pytest.MonkeyPatch):
9296 second = await read_stream .receive ()
9397 assert isinstance (second , SessionMessage )
9498 assert second .message == valid
99+
100+ sys .stdin .close ()
101+ sys .stdout .close ()
102+
103+
104+ @pytest .mark .anyio
105+ async def test_stdio_server_does_not_close_process_stdio (monkeypatch : pytest .MonkeyPatch ):
106+ """Default stdio_server() teardown must not close the caller's stdio handles."""
107+ valid = JSONRPCRequest (jsonrpc = "2.0" , id = 1 , method = "ping" )
108+ raw_stdin = tempfile .TemporaryFile ("w+b" )
109+ raw_stdin .write (valid .model_dump_json (by_alias = True , exclude_none = True ).encode () + b"\n " )
110+ raw_stdin .seek (0 )
111+ raw_stdout = tempfile .TemporaryFile ("w+b" )
112+
113+ monkeypatch .setattr (sys , "stdin" , TextIOWrapper (raw_stdin , encoding = "utf-8" ))
114+ monkeypatch .setattr (sys , "stdout" , TextIOWrapper (raw_stdout , encoding = "utf-8" ))
115+
116+ with anyio .fail_after (5 ):
117+ async with stdio_server () as (read_stream , write_stream ):
118+ await write_stream .aclose ()
119+ async with read_stream : # pragma: no branch
120+ received = await read_stream .receive ()
121+ assert isinstance (received , SessionMessage )
122+ assert received .message == valid
123+
124+ assert not sys .stdin .closed
125+ assert not sys .stdout .closed
126+
127+ sys .stdout .write ("still-open" )
128+ sys .stdout .flush ()
129+ raw_stdout .seek (0 )
130+ assert raw_stdout .read () == b"still-open"
131+
132+ sys .stdin .close ()
133+ sys .stdout .close ()
0 commit comments