diff --git a/.changeset/pty-handle-send-input.md b/.changeset/pty-handle-send-input.md new file mode 100644 index 0000000000..cecfc728e9 --- /dev/null +++ b/.changeset/pty-handle-send-input.md @@ -0,0 +1,8 @@ +--- +"@e2b/python-sdk": patch +"e2b": patch +--- + +Support sending input to a PTY directly through its handle with `handle.sendInput()` / `handle.send_input()`. Previously input could only be sent via the PID-keyed `sandbox.pty.sendInput()` / `sandbox.pty.send_stdin()`, and calling `handle.send_stdin()` on a PTY raised "Sending stdin is not supported for this command handle." It now raises an error pointing to `send_input()`, since a PTY has no stdin. PTY input methods also accept `str` input, encoded as UTF-8. + +In the Python SDK, `sandbox.pty.send_input()` is added to match the JS SDK; `sandbox.pty.send_stdin()` remains as a deprecated alias. diff --git a/packages/js-sdk/src/sandbox/commands/commandHandle.ts b/packages/js-sdk/src/sandbox/commands/commandHandle.ts index 3a0f46b544..b0585b0709 100644 --- a/packages/js-sdk/src/sandbox/commands/commandHandle.ts +++ b/packages/js-sdk/src/sandbox/commands/commandHandle.ts @@ -142,7 +142,11 @@ export class CommandHandle opts?: CommandRequestOpts ) => Promise, private readonly checkHealth?: SandboxHealthCheck, - retainOutput = true + retainOutput = true, + private readonly handleSendInput?: ( + data: string | Uint8Array, + opts?: CommandRequestOpts + ) => Promise ) { if (retainOutput) { this._stdout = '' @@ -248,13 +252,39 @@ export class CommandHandle ): Promise { if (!this.handleSendStdin) { throw new SandboxError( - 'Sending stdin is not supported for this command handle.' + this.handleSendInput + ? 'PTY handles have no stdin, use sendInput() to send input to the PTY instead.' + : 'Sending stdin is not supported for this command handle.' ) } await this.handleSendStdin(data, opts) } + /** + * Send input to the PTY. + * + * Only available on handles returned by `sandbox.pty.create()` and `sandbox.pty.connect()`. + * Strings are encoded as UTF-8. + * + * @param data input data to send to the PTY. + * @param opts connection options. + */ + async sendInput( + data: string | Uint8Array, + opts?: CommandRequestOpts + ): Promise { + if (!this.handleSendInput) { + throw new SandboxError( + this.handleSendStdin + ? 'Sending input is only supported for PTY handles, use sendStdin() to send data to the command stdin instead.' + : 'Sending input is not supported for this command handle.' + ) + } + + await this.handleSendInput(data, opts) + } + /** * Close the command stdin. * diff --git a/packages/js-sdk/src/sandbox/commands/pty.ts b/packages/js-sdk/src/sandbox/commands/pty.ts index e7f930e328..7d170e283a 100644 --- a/packages/js-sdk/src/sandbox/commands/pty.ts +++ b/packages/js-sdk/src/sandbox/commands/pty.ts @@ -153,7 +153,9 @@ export class Pty { opts.onData, undefined, undefined, - this.checkHealth + this.checkHealth, + undefined, + (data, inputOpts) => this.sendInput(pid, data, inputOpts) ) } catch (err) { cleanup() @@ -210,7 +212,9 @@ export class Pty { opts?.onData, undefined, undefined, - this.checkHealth + this.checkHealth, + undefined, + (data, inputOpts) => this.sendInput(pid, data, inputOpts) ) } catch (err) { cleanup() @@ -227,7 +231,7 @@ export class Pty { */ async sendInput( pid: number, - data: Uint8Array, + data: string | Uint8Array, opts?: Pick ): Promise { try { @@ -236,7 +240,10 @@ export class Pty { input: { input: { case: 'pty', - value: data, + value: + typeof data === 'string' + ? new TextEncoder().encode(data) + : data, }, }, process: { diff --git a/packages/js-sdk/tests/sandbox/commands/commandHandle.test.ts b/packages/js-sdk/tests/sandbox/commands/commandHandle.test.ts index 5a35df4424..553aa3fa57 100644 --- a/packages/js-sdk/tests/sandbox/commands/commandHandle.test.ts +++ b/packages/js-sdk/tests/sandbox/commands/commandHandle.test.ts @@ -542,3 +542,59 @@ describe('CommandHandle', () => { expect(err.stderr).toBeUndefined() }) }) + +describe('CommandHandle input routing', () => { + async function* noEvents() {} + + function ptyHandle( + sendInput: (data: string | Uint8Array) => Promise + ): CommandHandle { + return new CommandHandle( + 1, + () => {}, + async () => true, + noEvents(), + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + sendInput + ) + } + + it('sends PTY input through sendInput', async () => { + const sent: (string | Uint8Array)[] = [] + const handle = ptyHandle(async (data) => { + sent.push(data) + }) + + await handle.sendInput('ls\n') + await handle.sendInput(new Uint8Array([1, 2])) + + expect(sent).toEqual(['ls\n', new Uint8Array([1, 2])]) + }) + + it('points PTY handles from sendStdin to sendInput', async () => { + const handle = ptyHandle(async () => {}) + + await expect(handle.sendStdin('ls\n')).rejects.toThrow(/use sendInput\(\)/) + }) + + it('points command handles from sendInput to sendStdin', async () => { + const handle = new CommandHandle( + 1, + () => {}, + async () => true, + noEvents(), + undefined, + undefined, + undefined, + async () => {} + ) + + await expect(handle.sendInput('ls\n')).rejects.toThrow(/use sendStdin\(\)/) + }) +}) diff --git a/packages/js-sdk/tests/sandbox/pty/sendInput.test.ts b/packages/js-sdk/tests/sandbox/pty/sendInput.test.ts index c64c2fbee5..5e2aa983d2 100644 --- a/packages/js-sdk/tests/sandbox/pty/sendInput.test.ts +++ b/packages/js-sdk/tests/sandbox/pty/sendInput.test.ts @@ -13,3 +13,37 @@ sandboxTest('send input', async ({ sandbox }) => { await terminal.wait() expect(terminal.exitCode).toBe(0) }) + +sandboxTest('send string input', async ({ sandbox }) => { + const terminal = await sandbox.pty.create({ + cols: 80, + rows: 24, + onData: () => null, + }) + + await sandbox.pty.sendInput(terminal.pid, 'exit\n') + + await terminal.wait() + expect(terminal.exitCode).toBe(0) +}) + +sandboxTest('send input through handle', async ({ sandbox }) => { + let output = '' + + const terminal = await sandbox.pty.create({ + cols: 80, + rows: 24, + envs: { ABC: '123' }, + onData: (data) => { + output += new TextDecoder().decode(data) + }, + }) + + // Send input directly through the handle instead of the PID-keyed module method. + await terminal.sendInput(new TextEncoder().encode('echo $ABC\n')) + await terminal.sendInput('exit\n') + + await terminal.wait() + expect(terminal.exitCode).toBe(0) + expect(output).toContain('123') +}) diff --git a/packages/python-sdk/e2b/sandbox_async/commands/command_handle.py b/packages/python-sdk/e2b/sandbox_async/commands/command_handle.py index ea30c7d87a..536ed533d3 100644 --- a/packages/python-sdk/e2b/sandbox_async/commands/command_handle.py +++ b/packages/python-sdk/e2b/sandbox_async/commands/command_handle.py @@ -104,11 +104,15 @@ def __init__( ] = None, check_health: Optional[Callable[[], Awaitable[Optional[bool]]]] = None, retain_output: bool = True, + handle_send_input: Optional[ + Callable[[Union[str, bytes], Optional[float]], Coroutine[Any, Any, None]] + ] = None, ): self._pid = pid self._handle_kill = handle_kill self._handle_send_stdin = handle_send_stdin self._handle_close_stdin = handle_close_stdin + self._handle_send_input = handle_send_input self._check_health = check_health self._events = events @@ -304,11 +308,38 @@ async def send_stdin( :param request_timeout: Timeout for the request in **seconds** """ if self._handle_send_stdin is None: + if self._handle_send_input is not None: + raise SandboxException( + "PTY handles have no stdin, use send_input() to send input to the PTY instead." + ) raise SandboxException( "Sending stdin is not supported for this command handle." ) await self._handle_send_stdin(data, request_timeout) + async def send_input( + self, + data: Union[str, bytes], + request_timeout: Optional[float] = None, + ) -> None: + """ + Send input to the PTY. + + Only available on handles returned by `sandbox.pty.create()` and `sandbox.pty.connect()`. + + :param data: Input data to send, `str` is encoded as UTF-8 + :param request_timeout: Timeout for the request in **seconds** + """ + if self._handle_send_input is None: + if self._handle_send_stdin is not None: + raise SandboxException( + "Sending input is only supported for PTY handles, use send_stdin() to send data to the command stdin instead." + ) + raise SandboxException( + "Sending input is not supported for this command handle." + ) + await self._handle_send_input(data, request_timeout) + async def close_stdin(self, request_timeout: Optional[float] = None) -> None: """ Close the command stdin. diff --git a/packages/python-sdk/e2b/sandbox_async/commands/pty.py b/packages/python-sdk/e2b/sandbox_async/commands/pty.py index d7c1a4b29a..49825ad818 100644 --- a/packages/python-sdk/e2b/sandbox_async/commands/pty.py +++ b/packages/python-sdk/e2b/sandbox_async/commands/pty.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional +from typing import Dict, Optional, Union import httpx @@ -85,14 +85,31 @@ async def kill( async def send_stdin( self, pid: int, - data: bytes, + data: Union[str, bytes], + request_timeout: Optional[float] = None, + ) -> None: + """ + Send input to a PTY. + + :deprecated: Use `send_input()` instead. PTYs have no stdin; this sends input to the PTY. + + :param pid: Process ID of the PTY + :param data: Input data to send, `str` is encoded as UTF-8 + :param request_timeout: Timeout for the request in **seconds** + """ + return await self.send_input(pid, data, request_timeout) + + async def send_input( + self, + pid: int, + data: Union[str, bytes], request_timeout: Optional[float] = None, ) -> None: """ Send input to a PTY. :param pid: Process ID of the PTY - :param data: Input data to send + :param data: Input data to send, `str` is encoded as UTF-8 :param request_timeout: Timeout for the request in **seconds** """ try: @@ -100,7 +117,9 @@ async def send_stdin( process_pb.SendInputRequest( process=process_pb.ProcessSelector(selector=Oneof("pid", pid)), input=process_pb.ProcessInput( - input=Oneof("pty", data), + input=Oneof( + "pty", data.encode() if isinstance(data, str) else data + ), ), ), timeout_ms=timeout_to_ms( @@ -195,6 +214,9 @@ async def create( handle_kill=lambda: self.kill(pid), events=events, on_pty=on_data, + handle_send_input=lambda data, request_timeout=None: self.send_input( + pid, data, request_timeout + ), check_health=self._check_health, ) except Exception as e: @@ -244,6 +266,9 @@ async def connect( handle_kill=lambda: self.kill(pid), events=events, on_pty=on_data, + handle_send_input=lambda data, request_timeout=None: self.send_input( + pid, data, request_timeout + ), check_health=self._check_health, ) except Exception as e: diff --git a/packages/python-sdk/e2b/sandbox_sync/commands/command_handle.py b/packages/python-sdk/e2b/sandbox_sync/commands/command_handle.py index 6d38cd0f93..c6fc253376 100644 --- a/packages/python-sdk/e2b/sandbox_sync/commands/command_handle.py +++ b/packages/python-sdk/e2b/sandbox_sync/commands/command_handle.py @@ -45,11 +45,15 @@ def __init__( handle_close_stdin: Optional[Callable[[Optional[float]], None]] = None, check_health: Optional[Callable[[], Optional[bool]]] = None, retain_output: bool = True, + handle_send_input: Optional[ + Callable[[Union[str, bytes], Optional[float]], None] + ] = None, ): self._pid = pid self._handle_kill = handle_kill self._handle_send_stdin = handle_send_stdin self._handle_close_stdin = handle_close_stdin + self._handle_send_input = handle_send_input self._check_health = check_health self._events = events @@ -240,11 +244,38 @@ def send_stdin( :param request_timeout: Timeout for the request in **seconds** """ if self._handle_send_stdin is None: + if self._handle_send_input is not None: + raise SandboxException( + "PTY handles have no stdin, use send_input() to send input to the PTY instead." + ) raise SandboxException( "Sending stdin is not supported for this command handle." ) self._handle_send_stdin(data, request_timeout) + def send_input( + self, + data: Union[str, bytes], + request_timeout: Optional[float] = None, + ) -> None: + """ + Send input to the PTY. + + Only available on handles returned by `sandbox.pty.create()` and `sandbox.pty.connect()`. + + :param data: Input data to send, `str` is encoded as UTF-8 + :param request_timeout: Timeout for the request in **seconds** + """ + if self._handle_send_input is None: + if self._handle_send_stdin is not None: + raise SandboxException( + "Sending input is only supported for PTY handles, use send_stdin() to send data to the command stdin instead." + ) + raise SandboxException( + "Sending input is not supported for this command handle." + ) + self._handle_send_input(data, request_timeout) + def close_stdin(self, request_timeout: Optional[float] = None) -> None: """ Close the command stdin. diff --git a/packages/python-sdk/e2b/sandbox_sync/commands/pty.py b/packages/python-sdk/e2b/sandbox_sync/commands/pty.py index 609d2f2ac0..de8c89b6e5 100644 --- a/packages/python-sdk/e2b/sandbox_sync/commands/pty.py +++ b/packages/python-sdk/e2b/sandbox_sync/commands/pty.py @@ -81,14 +81,31 @@ def kill( def send_stdin( self, pid: int, - data: bytes, + data: Union[str, bytes], + request_timeout: Optional[float] = None, + ) -> None: + """ + Send input to a PTY. + + :deprecated: Use `send_input()` instead. PTYs have no stdin; this sends input to the PTY. + + :param pid: Process ID of the PTY + :param data: Input data to send, `str` is encoded as UTF-8 + :param request_timeout: Timeout for the request in **seconds** + """ + return self.send_input(pid, data, request_timeout) + + def send_input( + self, + pid: int, + data: Union[str, bytes], request_timeout: Optional[float] = None, ) -> None: """ Send input to a PTY. :param pid: Process ID of the PTY - :param data: Input data to send + :param data: Input data to send, `str` is encoded as UTF-8 :param request_timeout: Timeout for the request in **seconds** """ try: @@ -96,7 +113,9 @@ def send_stdin( process_pb.SendInputRequest( process=process_pb.ProcessSelector(selector=Oneof("pid", pid)), input=process_pb.ProcessInput( - input=Oneof("pty", data), + input=Oneof( + "pty", data.encode() if isinstance(data, str) else data + ), ), ), timeout_ms=timeout_to_ms( @@ -185,6 +204,9 @@ def create( pid=pid, handle_kill=lambda: self.kill(pid), events=events, + handle_send_input=lambda data, request_timeout=None: self.send_input( + pid, data, request_timeout + ), check_health=self._check_health, ) except Exception as e: @@ -229,6 +251,9 @@ def connect( pid=pid, handle_kill=lambda: self.kill(pid), events=events, + handle_send_input=lambda data, request_timeout=None: self.send_input( + pid, data, request_timeout + ), check_health=self._check_health, ) except Exception as e: diff --git a/packages/python-sdk/tests/async/sandbox_async/pty/test_pty_connect.py b/packages/python-sdk/tests/async/sandbox_async/pty/test_pty_connect.py index 166ebe4653..a689f8b2c2 100644 --- a/packages/python-sdk/tests/async/sandbox_async/pty/test_pty_connect.py +++ b/packages/python-sdk/tests/async/sandbox_async/pty/test_pty_connect.py @@ -18,7 +18,7 @@ def append_data(data: list, x: bytes): envs={"FOO": "bar"}, ) - await async_sandbox.pty.send_stdin(terminal.pid, b"echo $FOO\n") + await async_sandbox.pty.send_input(terminal.pid, b"echo $FOO\n") # Give time for the command output in the first connection await asyncio.sleep(0.3) @@ -30,7 +30,7 @@ def append_data(data: list, x: bytes): terminal.pid, on_data=lambda x: append_data(output2, x) ) - await async_sandbox.pty.send_stdin(terminal.pid, b"echo $FOO\nexit\n") + await async_sandbox.pty.send_input(terminal.pid, b"echo $FOO\nexit\n") await reconnect_handle.wait() diff --git a/packages/python-sdk/tests/async/sandbox_async/pty/test_pty_create.py b/packages/python-sdk/tests/async/sandbox_async/pty/test_pty_create.py index eec044dec0..ca12957358 100644 --- a/packages/python-sdk/tests/async/sandbox_async/pty/test_pty_create.py +++ b/packages/python-sdk/tests/async/sandbox_async/pty/test_pty_create.py @@ -12,8 +12,8 @@ def append_data(data: list, x: bytes): PtySize(80, 24), on_data=lambda x: append_data(output, x), envs={"ABC": "123"} ) - await async_sandbox.pty.send_stdin(terminal.pid, b"echo $ABC\n") - await async_sandbox.pty.send_stdin(terminal.pid, b"exit\n") + await async_sandbox.pty.send_input(terminal.pid, b"echo $ABC\n") + await async_sandbox.pty.send_input(terminal.pid, b"exit\n") await terminal.wait() assert terminal.exit_code == 0 diff --git a/packages/python-sdk/tests/async/sandbox_async/pty/test_resize.py b/packages/python-sdk/tests/async/sandbox_async/pty/test_resize.py index ca91ff64a3..1c387b4e8f 100644 --- a/packages/python-sdk/tests/async/sandbox_async/pty/test_resize.py +++ b/packages/python-sdk/tests/async/sandbox_async/pty/test_resize.py @@ -12,8 +12,8 @@ def append_data(data: list, x: bytes): PtySize(cols=80, rows=24), on_data=lambda x: append_data(output, x) ) - await async_sandbox.pty.send_stdin(terminal.pid, b"tput cols\n") - await async_sandbox.pty.send_stdin(terminal.pid, b"exit\n") + await async_sandbox.pty.send_input(terminal.pid, b"tput cols\n") + await async_sandbox.pty.send_input(terminal.pid, b"exit\n") await terminal.wait() assert terminal.exit_code == 0 @@ -26,8 +26,8 @@ def append_data(data: list, x: bytes): ) await async_sandbox.pty.resize(terminal.pid, PtySize(cols=100, rows=24)) - await async_sandbox.pty.send_stdin(terminal.pid, b"tput cols\n") - await async_sandbox.pty.send_stdin(terminal.pid, b"exit\n") + await async_sandbox.pty.send_input(terminal.pid, b"tput cols\n") + await async_sandbox.pty.send_input(terminal.pid, b"exit\n") await terminal.wait() assert terminal.exit_code == 0 diff --git a/packages/python-sdk/tests/async/sandbox_async/pty/test_send_input.py b/packages/python-sdk/tests/async/sandbox_async/pty/test_send_input.py index d54eeaa09f..e2de66ad9f 100644 --- a/packages/python-sdk/tests/async/sandbox_async/pty/test_send_input.py +++ b/packages/python-sdk/tests/async/sandbox_async/pty/test_send_input.py @@ -6,6 +6,33 @@ async def test_send_input(async_sandbox: AsyncSandbox): terminal = await async_sandbox.pty.create( PtySize(cols=80, rows=24), on_data=lambda x: print(x) ) - await async_sandbox.pty.send_stdin(terminal.pid, b"exit\n") + await async_sandbox.pty.send_input(terminal.pid, b"exit\n") + await terminal.wait() + assert terminal.exit_code == 0 + + +async def test_handle_send_input(async_sandbox: AsyncSandbox): + output = [] + + terminal = await async_sandbox.pty.create( + PtySize(cols=80, rows=24), + on_data=lambda x: output.append(x.decode("utf-8")), + envs={"ABC": "123"}, + ) + + # Send input directly through the handle instead of the PID-keyed module method. + await terminal.send_input(b"echo $ABC\n") + await terminal.send_input("exit\n") + + await terminal.wait() + assert terminal.exit_code == 0 + assert "123" in "".join(output) + + +async def test_send_stdin_deprecated_alias(async_sandbox: AsyncSandbox): + terminal = await async_sandbox.pty.create( + PtySize(cols=80, rows=24), on_data=lambda x: None + ) + await async_sandbox.pty.send_stdin(terminal.pid, "exit\n") await terminal.wait() assert terminal.exit_code == 0 diff --git a/packages/python-sdk/tests/sync/sandbox_sync/pty/test_pty.py b/packages/python-sdk/tests/sync/sandbox_sync/pty/test_pty.py index 1dd6255ba0..a4fdc02c69 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/pty/test_pty.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/pty/test_pty.py @@ -8,7 +8,7 @@ def append_data(data: list, x: bytes): terminal = sandbox.pty.create(PtySize(80, 24), envs={"ABC": "123"}, cwd="/") - sandbox.pty.send_stdin(terminal.pid, b"echo $ABC\nexit\n") + sandbox.pty.send_input(terminal.pid, b"echo $ABC\nexit\n") output = [] result = terminal.wait(on_pty=lambda x: append_data(output, x)) diff --git a/packages/python-sdk/tests/sync/sandbox_sync/pty/test_pty_connect.py b/packages/python-sdk/tests/sync/sandbox_sync/pty/test_pty_connect.py index bfabbc7738..9243571e14 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/pty/test_pty_connect.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/pty/test_pty_connect.py @@ -13,14 +13,14 @@ def append_data(data: list, x: bytes): terminal = sandbox.pty.create(PtySize(80, 24), envs={"FOO": "bar"}) - sandbox.pty.send_stdin(terminal.pid, b"echo $FOO\n") + sandbox.pty.send_input(terminal.pid, b"echo $FOO\n") terminal.disconnect() # Now connect again, with a new on_pty handler reconnect_handle = sandbox.pty.connect(terminal.pid) - sandbox.pty.send_stdin(terminal.pid, b"echo $FOO\nexit\n") + sandbox.pty.send_input(terminal.pid, b"echo $FOO\nexit\n") result = reconnect_handle.wait(on_pty=lambda x: append_data(output, x)) diff --git a/packages/python-sdk/tests/sync/sandbox_sync/pty/test_resize.py b/packages/python-sdk/tests/sync/sandbox_sync/pty/test_resize.py index 726b34ee1a..fba481355d 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/pty/test_resize.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/pty/test_resize.py @@ -8,7 +8,7 @@ def append_data(data: list, x: bytes): terminal = sandbox.pty.create(PtySize(cols=80, rows=24)) - sandbox.pty.send_stdin(terminal.pid, b"tput cols\nexit\n") + sandbox.pty.send_input(terminal.pid, b"tput cols\nexit\n") output = [] result = terminal.wait(on_pty=lambda x: append_data(output, x)) @@ -19,7 +19,7 @@ def append_data(data: list, x: bytes): terminal = sandbox.pty.create(PtySize(cols=80, rows=24)) sandbox.pty.resize(terminal.pid, PtySize(cols=100, rows=24)) - sandbox.pty.send_stdin(terminal.pid, b"tput cols\nexit\n") + sandbox.pty.send_input(terminal.pid, b"tput cols\nexit\n") output = [] result = terminal.wait(on_pty=lambda x: append_data(output, x)) diff --git a/packages/python-sdk/tests/sync/sandbox_sync/pty/test_send_input.py b/packages/python-sdk/tests/sync/sandbox_sync/pty/test_send_input.py index 15c5df0344..8b7ceafa6f 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/pty/test_send_input.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/pty/test_send_input.py @@ -4,6 +4,27 @@ def test_send_input(sandbox: Sandbox): terminal = sandbox.pty.create(PtySize(cols=80, rows=24)) - sandbox.pty.send_stdin(terminal.pid, b"exit\n") + sandbox.pty.send_input(terminal.pid, b"exit\n") + result = terminal.wait() + assert result.exit_code == 0 + + +def test_handle_send_input(sandbox: Sandbox): + output = [] + + terminal = sandbox.pty.create(PtySize(cols=80, rows=24), envs={"ABC": "123"}) + + # Send input directly through the handle instead of the PID-keyed module method. + terminal.send_input(b"echo $ABC\n") + terminal.send_input("exit\n") + + result = terminal.wait(on_pty=lambda x: output.append(x.decode("utf-8"))) + assert result.exit_code == 0 + assert "123" in "".join(output) + + +def test_send_stdin_deprecated_alias(sandbox: Sandbox): + terminal = sandbox.pty.create(PtySize(cols=80, rows=24)) + sandbox.pty.send_stdin(terminal.pid, "exit\n") result = terminal.wait() assert result.exit_code == 0 diff --git a/packages/python-sdk/tests/test_command_handle.py b/packages/python-sdk/tests/test_command_handle.py index bbdf8ece0c..dadc0d4ce7 100644 --- a/packages/python-sdk/tests/test_command_handle.py +++ b/packages/python-sdk/tests/test_command_handle.py @@ -6,6 +6,7 @@ from protobuf import Oneof from e2b.envd.process import process_pb +from e2b.exceptions import SandboxException from e2b.sandbox.commands.command_handle import CommandExitException from e2b.sandbox_async.commands.command_handle import AsyncCommandHandle from e2b.sandbox_sync.commands.command_handle import CommandHandle @@ -365,3 +366,58 @@ async def events(): assert exc_info.value.stdout is None assert exc_info.value.stderr is None + + +def test_sync_pty_handle_send_input(): + sent = [] + handle = CommandHandle( + pid=1, + handle_kill=lambda: True, + events=iter([]), + handle_send_input=lambda data, request_timeout=None: sent.append(data), + ) + + handle.send_input("ls\n") + handle.send_input(b"\x01") + assert sent == ["ls\n", b"\x01"] + + with pytest.raises(SandboxException, match=r"use send_input\(\)"): + handle.send_stdin("ls\n") + + +def test_sync_command_handle_send_input_points_to_send_stdin(): + handle = CommandHandle( + pid=1, + handle_kill=lambda: True, + events=iter([]), + handle_send_stdin=lambda data, request_timeout=None: None, + ) + + with pytest.raises(SandboxException, match=r"use send_stdin\(\)"): + handle.send_input("ls\n") + + +async def test_async_pty_handle_send_input(): + sent = [] + + async def send_input(data, request_timeout=None): + sent.append(data) + + async def no_events(): + return + yield + + handle = AsyncCommandHandle( + pid=1, + handle_kill=_kill, + events=no_events(), + handle_send_input=send_input, + ) + + await handle.send_input("ls\n") + await handle.send_input(b"\x01") + assert sent == ["ls\n", b"\x01"] + + with pytest.raises(SandboxException, match=r"use send_input\(\)"): + await handle.send_stdin("ls\n") + await handle.disconnect()