|
43 | 43 | "import sys; [(sys.stdout.write(line.upper()), sys.stdout.flush()) for line in sys.stdin]" |
44 | 44 | ) |
45 | 45 |
|
| 46 | +# Echoes the first raw stdin byte to stdout. |
| 47 | +_ECHO_ONE_STDIN_BYTE = ( |
| 48 | + "import sys; " |
| 49 | + "data = sys.stdin.buffer.read(1); " |
| 50 | + "sys.stdout.buffer.write(data); " |
| 51 | + "sys.stdout.buffer.flush()" |
| 52 | +) |
| 53 | + |
46 | 54 | # stdout + stderr on both streams. |
47 | 55 | _BOTH_STREAMS = ( |
48 | 56 | "import sys; " |
@@ -223,6 +231,32 @@ async def scenario() -> list[str]: |
223 | 231 | assert asyncio.run(scenario()) == ["FROM-BYTEARRAY", "FROM-MEMORYVIEW"] |
224 | 232 |
|
225 | 233 |
|
| 234 | +def test_interactive_stdin_send_control_writes_control_byte() -> None: |
| 235 | + async def scenario() -> bytes: |
| 236 | + proc = await Command(PY, ["-c", _ECHO_ONE_STDIN_BYTE]).keep_stdin_open().astart() |
| 237 | + stdin = proc.take_stdin() |
| 238 | + await stdin.send_control("d") |
| 239 | + await stdin.close() |
| 240 | + result = await proc.aoutput_bytes() |
| 241 | + return result.stdout |
| 242 | + |
| 243 | + assert asyncio.run(scenario()) == b"\x04" |
| 244 | + |
| 245 | + |
| 246 | +def test_interactive_stdin_send_control_rejects_invalid_argument() -> None: |
| 247 | + async def scenario() -> None: |
| 248 | + proc = await Command(PY, ["-c", _ECHO_ONE_STDIN_BYTE]).keep_stdin_open().astart() |
| 249 | + stdin = proc.take_stdin() |
| 250 | + with pytest.raises(ValueError): |
| 251 | + await stdin.send_control("0") |
| 252 | + with pytest.raises(ValueError): |
| 253 | + await stdin.send_control("cc") |
| 254 | + await stdin.close() |
| 255 | + await proc.aoutcome() |
| 256 | + |
| 257 | + asyncio.run(scenario()) |
| 258 | + |
| 259 | + |
226 | 260 | def test_take_stdin_is_once() -> None: |
227 | 261 | # The first take hands over the handle; a second take raises (consumed). |
228 | 262 | async def scenario() -> None: |
|
0 commit comments