Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/colab_cli/commands/ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import contextlib
import os
from pathlib import Path
import select
import shlex
import signal
import subprocess
Expand Down Expand Up @@ -360,9 +359,6 @@ def _bridge_proxy_mode(ws: websocket.WebSocket) -> int:
def stdin_to_ws():
try:
while True:
ready, _, _ = select.select([stdin_fd], [], [], None)
if not ready:
continue
data = os.read(stdin_fd, 8192)
if not data:
break
Expand Down
44 changes: 44 additions & 0 deletions tests/test_ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import shlex
import subprocess
import sys
import threading
from unittest.mock import MagicMock

from colab_cli.cli import app
Expand Down Expand Up @@ -235,6 +236,49 @@ def test_bridge_proxy_mode_pumps_ws_to_stdout(mocker):
ws.close.assert_called()


def test_bridge_proxy_mode_pumps_stdin_to_ws(mocker):
"""os.read() chunks leave as binary frames; EOF ends the pump and closes
the socket.

Regression test: the pump used select() on the stdin fd before reading.
Windows select() only accepts sockets, so there the OSError killed the
pump thread and the remote side saw no input at all; a plain blocking
read works on every platform.
"""
reads = iter([b"ping ", b"hello", b""])
stdin_eof = threading.Event()

def fake_read(fd, size):
chunk = next(reads)
if not chunk:
stdin_eof.set()
return chunk

mocker.patch("sys.stdin")
sys.stdin.buffer.fileno.return_value = 0
mocker.patch.object(ssh_module.os, "read", side_effect=fake_read)
fake_stdout = MagicMock()
fake_stdout.buffer = io.BytesIO()
mocker.patch("sys.stdout", fake_stdout)

# Hold the ws->stdout loop until the pump has drained stdin, so the
# send_binary assertions below can't race the reader thread.
def recv_data(control_frame):
stdin_eof.wait(timeout=5)
return (websocket.ABNF.OPCODE_CLOSE, b"")

ws = MagicMock()
ws.recv_data.side_effect = recv_data

rc = ssh_module._bridge_proxy_mode(ws)
assert rc == 0
assert [call.args[0] for call in ws.send_binary.call_args_list] == [
b"ping ",
b"hello",
]
ws.close.assert_called()


# --- ProxyCommand shell quoting ---------------------------------------------


Expand Down
Loading