Skip to content

Commit

Permalink
Allow stdio to be None in processes (#82)
Browse files Browse the repository at this point in the history
  • Loading branch information
twizmwazin authored Feb 9, 2024
1 parent 3860df6 commit e700353
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 29 deletions.
29 changes: 19 additions & 10 deletions python/binharness/agentenvironment.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,22 +115,31 @@ def __init__( # noqa: PLR0913
self._pid = pid

@cached_property
def stdin(self: AgentProcess) -> AgentIO:
def stdin(self: AgentProcess) -> AgentIO | None:
"""Get the standard input stream of the process."""
fd = self._client.get_process_channel(self._env_id, self._pid, 0)
return AgentIO(self._client, self._env_id, fd)
try:
fd = self._client.get_process_channel(self._env_id, self._pid, 0)
return AgentIO(self._client, self._env_id, fd)
except RuntimeError:
return None # TODO: verify that this is the right error

@cached_property
def stdout(self: AgentProcess) -> AgentIO:
def stdout(self: AgentProcess) -> AgentIO | None:
"""Get the standard output stream of the process."""
fd = self._client.get_process_channel(self._env_id, self._pid, 1)
return AgentIO(self._client, self._env_id, fd)
try:
fd = self._client.get_process_channel(self._env_id, self._pid, 1)
return AgentIO(self._client, self._env_id, fd)
except RuntimeError:
return None # TODO: verify that this is the right error

@cached_property
def stderr(self: AgentProcess) -> AgentIO:
def stderr(self: AgentProcess) -> AgentIO | None:
"""Get the standard error stream of the process."""
fd = self._client.get_process_channel(self._env_id, self._pid, 2)
return AgentIO(self._client, self._env_id, fd)
try:
fd = self._client.get_process_channel(self._env_id, self._pid, 2)
return AgentIO(self._client, self._env_id, fd)
except RuntimeError:
return None # TODO: verify that this is the right error

@property
def returncode(self: AgentProcess) -> int | None:
Expand Down Expand Up @@ -174,7 +183,7 @@ def run_command(
stdin=True,
stdout=True,
stderr=True,
executable=str(args[0]),
executable=str(normalized_args[0]),
env=list(env.items()) if env else None,
cwd=str(cwd) if cwd else None,
setuid=None,
Expand Down
5 changes: 3 additions & 2 deletions python/binharness/common/busybox.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from os import environ
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, cast

from binharness.types import InjectableExecutor, Target
from binharness.types.injection import ExecutableInjection
Expand Down Expand Up @@ -48,7 +48,8 @@ def mktemp(
"""Run mktemp in the environment and returns the Path created."""
proc = self.run("mktemp", "-d") if directory else self.run("mktemp")
stdout, _ = proc.communicate()
return Path(stdout.decode().strip())
# TODO: Find out how to not cast
return Path(cast(bytes, stdout).decode().strip())

def shell(
self: BusyboxInjection, command: str, env: dict[str, str] | None = None
Expand Down
12 changes: 3 additions & 9 deletions python/binharness/localenvironment.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,24 +110,18 @@ def __init__(
)

@property
def stdin(self: LocalProcess) -> IO[bytes]:
def stdin(self: LocalProcess) -> IO[bytes] | None:
"""Get the standard input stream of the process."""
if self.popen.stdin is None:
raise ValueError # pragma: no cover
return self.popen.stdin

@property
def stdout(self: LocalProcess) -> IO[bytes]:
def stdout(self: LocalProcess) -> IO[bytes] | None:
"""Get the standard output stream of the process."""
if self.popen.stdout is None:
raise ValueError # pragma: no cover
return self.popen.stdout

@property
def stderr(self: LocalProcess) -> IO[bytes]:
def stderr(self: LocalProcess) -> IO[bytes] | None:
"""Get the standard error stream of the process."""
if self.popen.stderr is None:
raise ValueError # pragma: no cover
return self.popen.stderr

@property
Expand Down
19 changes: 11 additions & 8 deletions python/binharness/types/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,17 @@ def __init__(
self.cwd = cwd or environment.get_tempdir()

@abstractproperty
def stdin(self: Process) -> IO[bytes]:
def stdin(self: Process) -> IO[bytes] | None:
"""Get the standard input stream of the process."""
raise NotImplementedError

@abstractproperty
def stdout(self: Process) -> IO[bytes]:
def stdout(self: Process) -> IO[bytes] | None:
"""Get the standard output stream of the process."""
raise NotImplementedError

@abstractproperty
def stderr(self: Process) -> IO[bytes]:
def stderr(self: Process) -> IO[bytes] | None:
"""Get the standard error stream of the process."""
raise NotImplementedError

Expand All @@ -65,10 +65,13 @@ def wait(self: Process, timeout: float | None = None) -> int:

def communicate(
self: Process, input_: bytes | None = None, timeout: float | None = None
) -> tuple[bytes, bytes]:
) -> tuple[bytes | None, bytes | None]:
"""Send input to the process and return its output and error streams."""
if input_ is not None:
self.stdin.write(input_)
self.stdin.close()
if self.stdin is not None:
if input_ is not None:
self.stdin.write(input_)
self.stdin.close()
self.wait(timeout)
return (self.stdout.read(), self.stderr.read())
stdout = self.stdout.read() if self.stdout is not None else None
stderr = self.stderr.read() if self.stderr is not None else None
return (stdout, stderr)
1 change: 1 addition & 0 deletions python/tests/bootstrap/test_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def test_bootstrap_env_from_image(

proc.wait()
assert proc.returncode == 0
assert proc.stdout is not None
assert proc.stdout.read() == b"hello world\n"
finally:
agent.container.kill()
1 change: 1 addition & 0 deletions python/tests/bootstrap/test_ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def test_bootstrap_ssh_environment_with_client(

proc.wait()
assert proc.returncode == 0
assert proc.stdout is not None
assert proc.stdout.read() == b"hello world\n"
finally:
agent.stop()
4 changes: 4 additions & 0 deletions python/tests/test_busybox.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,14 @@ def test_nc_interaction() -> None:
server_proc = busybox.nc("localhost", 10001, listen=True)
client_proc = busybox.nc("localhost", 10001)

assert client_proc.stdin is not None
assert server_proc.stdin is not None
client_proc.stdin.write(b"hello\n")
client_proc.stdin.flush()
server_proc.stdin.write(b"hello back\n")
server_proc.stdin.flush()

assert server_proc.stdout is not None
assert server_proc.stdout.readline() == b"hello\n"
assert client_proc.stdout is not None
assert client_proc.stdout.readline() == b"hello back\n"
4 changes: 4 additions & 0 deletions python/tests/test_localenvironment.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def test_stdout() -> None:
busybox = BusyboxInjection()
busybox.install(env)
proc = busybox.shell("echo hello")
assert proc.stdout is not None
assert proc.stdout.read() == b"hello\n"


Expand All @@ -52,6 +53,7 @@ def test_stderr() -> None:
busybox = BusyboxInjection()
busybox.install(env)
proc = busybox.shell("echo hello 1>&2")
assert proc.stderr is not None
assert proc.stderr.read() == b"hello\n"


Expand All @@ -62,8 +64,10 @@ def test_process_poll() -> None:
busybox.install(env)
proc = busybox.run("head")
assert proc.poll() is None
assert proc.stdin is not None
proc.stdin.write(b"hello\n")
proc.stdin.close()
proc.wait()
assert proc.poll() is not None
assert proc.stdout is not None
assert proc.stdout.read() == b"hello\n"

0 comments on commit e700353

Please sign in to comment.