Skip to content

Fix tests and refactor ssh#2

Merged
mesudip merged 4 commits into
masterfrom
dev
Feb 9, 2026
Merged

Fix tests and refactor ssh#2
mesudip merged 4 commits into
masterfrom
dev

Conversation

@mesudip
Copy link
Copy Markdown
Owner

@mesudip mesudip commented Feb 8, 2026

No description provided.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @mesudip, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request overhauls the SSH client functionality by introducing a dedicated server management class, enabling more flexible and robust interactions with remote SSH hosts. It also significantly expands the test suite with Docker-based integration tests and new examples, ensuring the reliability and demonstrating the capabilities of the updated SSH and RPC communication mechanisms.

Highlights

  • SSH Module Refactoring: The SSH integration has been significantly refactored. A new SshServer class is introduced to manage SSH client connections, handling authentication and allowing the creation of multiple SshPipe instances. The SshPipe class itself is simplified, now focusing on individual SSH channels or processes, and includes improved termination logic and stderr handling.
  • Enhanced Testing Infrastructure: Comprehensive Docker-based pytest fixtures have been added to set up and manage an SSH server for integration tests. This includes fixtures for SSH key generation, Docker client/network management, and robust SSH server readiness checks. Existing SSH tests have been updated to leverage this new infrastructure.
  • New RPC Communication Examples and Tests: Several new examples and integration tests demonstrate various RPC communication patterns over SSH, including simple command execution, RPC over SSH, and direct Unix socket connections. New test files cover RPC over local stdio, SSH stdio, and local/SSH Unix sockets, ensuring broad coverage of the new features.
  • AioPipe and RpcV1 Minor Improvements: The AioPipe now includes checks to prevent redundant connection setup and a write_eof method. Internal RpcV1 handler methods have been renamed for better encapsulation. TCP connection handling in tests has also been refined for reliability.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • .dockerignore
    • Added common development and build artifacts to ignore.
  • cbor_rpc/pipe/aio_pipe.py
    • Prevented redundant connection setup if already connected.
    • Added write_eof method to signal end of stream.
  • cbor_rpc/rpc/rpc_v1.py
    • Renamed internal handle_proto_X methods to _handle_proto_X for encapsulation.
  • cbor_rpc/ssh/init.py
    • Exported SshServer class.
  • cbor_rpc/ssh/ssh_pipe.py
    • SshPipe constructor simplified to take ssh_channel directly, removing ssh_client dependency.
    • Implemented _stderr_loop in SshPipe to capture and emit stderr output from remote processes.
    • Enhanced terminate method in SshPipe to send EOF, and gracefully/forcefully close SSH channels/processes.
    • Introduced SshServer class for managing SSH client connections, including connect, run_command, open_unix_connection, and close methods.
    • SshServer.connect now handles SSH key content and passphrase for authentication.
  • cbor_rpc/tcp/tcp.py
    • Modified create_connection_pair to use event-based connection handling and server._server.close() for shutdown.
    • Removed await self._server.wait_closed() from shutdown method.
  • conftest.py
    • Added ssh_keys fixture for generating SSH keys.
    • Added docker_client, test_network, docker_host_ip fixtures for Docker environment setup.
    • Added ssh_server fixture to provision and manage a Docker-based SSH server for tests.
  • examples/ssh_exec/README.md
    • Added documentation for SSH execution examples.
  • examples/ssh_exec/forwarding_example.py
    • Added example for SSH Unix socket forwarding.
  • examples/ssh_exec/rpc_client.py
    • Added example for RPC over SSH.
  • examples/ssh_exec/simple_exec.py
    • Added example for simple command execution over SSH.
  • examples/ssh_exec/stdio_rpc_server.py
    • Added example RPC server for stdio communication.
  • requirements.txt
    • Added ijson[yajl] dependency.
  • tests/conftest.py
    • Moved and refactored SSH-related fixtures to a dedicated tests/conftest.py for better test organization.
    • Introduced ssh_server fixture for SshServer instances.
  • tests/docker/sshd-python/Dockerfile
    • Updated to install Python build dependencies and cbor2, ijson, asyncssh.
    • Enabled AllowStreamLocalForwarding in sshd_config.
    • Added WORKDIR /app and ENV PYTHONPATH=/app.
    • Copied new server scripts (rpc_server.py, performance_server.py, unix_socket_bridge.py, unix_socket_rpc_wrapper.py) and the cbor_rpc library itself into the Docker image.
  • tests/docker/sshd-python/performance_server.py
    • Added a new RPC server for performance testing.
  • tests/docker/sshd-python/rpc_server.py
    • Added a new RPC server for Unix socket communication.
  • tests/docker/sshd-python/unix_socket_bridge.py
    • Added a script to bridge stdio to a Unix socket.
  • tests/docker/sshd-python/unix_socket_rpc_wrapper.py
    • Added a wrapper for RPC over Unix sockets via stdio.
  • tests/integration/rpc_test_helpers.py
    • Added helper functions for common RPC test scenarios.
  • tests/integration/test_rpc_ssh_stdio.py
    • Added integration test for RPC over SSH using stdio.
  • tests/integration/test_rpc_stdio.py
    • Added integration test for RPC over local stdio.
  • tests/integration/test_rpc_system.py
    • Added integration tests for RPC over local and SSH Unix sockets.
  • tests/pipe/test_aio_pipe.py
    • Renamed TestAioPipe to AioDummyPipe for clarity.
    • Updated test cases to use AioDummyPipe.
  • tests/rpc/test_rpc_v1_extra.py
    • Renamed TestRpcServer to TstRpcServer to avoid naming conflicts.
  • tests/scripts/stdio_rpc_server.py
    • Added a new stdio RPC server script for testing.
  • tests/ssh/test_ssh_docker_pipe.py
    • Refactored to use the new ssh_server fixture, simplifying test setup.
    • Added test_ssh_pipe_multiplexing to verify concurrent channel usage.
    • Removed redundant SSH connection and authentication tests, now covered by test_ssh_server.py.
  • tests/ssh/test_ssh_pipe.py
    • Simplified SshPipe test setup by removing DummyClient and directly passing DummyChannel.
  • tests/ssh/test_ssh_server.py
    • Added new tests specifically for SshServer's authentication methods (password, plain key, encrypted key).
  • tests/tcp/test_tcp.py
    • Increased DEFAULT_TIMEOUT for TCP tests.
    • Updated TcpPipe.create_connection timeout in test_tcp_connection_errors.
Activity
  • A new .dockerignore file has been introduced to manage ignored files in Docker builds.
  • The aio_pipe.py file was modified to enhance connection setup robustness and add an write_eof method.
  • Internal method names in rpc_v1.py were updated for better code organization.
  • The cbor_rpc/ssh module underwent a significant refactoring, introducing the SshServer class and enhancing SshPipe capabilities.
  • Minor adjustments were made to tcp.py to improve server shutdown logic.
  • The main conftest.py file was updated with extensive Docker and SSH server setup fixtures to support new integration tests.
  • New example files demonstrating SSH execution and RPC over SSH were added under examples/ssh_exec.
  • The requirements.txt file was updated to include ijson[yajl].
  • A dedicated tests/conftest.py was created to house SSH-related fixtures, improving test organization.
  • The Dockerfile for the SSH daemon in tests was updated to include necessary Python dependencies, enable Unix socket forwarding, and copy new server scripts.
  • Several new server scripts (performance_server.py, rpc_server.py, unix_socket_bridge.py, unix_socket_rpc_wrapper.py) were added to the Docker test environment.
  • New helper functions for RPC testing were added in tests/integration/rpc_test_helpers.py.
  • New integration tests were added for RPC over SSH stdio (test_rpc_ssh_stdio.py), local stdio (test_rpc_stdio.py), and local/SSH Unix sockets (test_rpc_system.py).
  • Existing test files for aio_pipe, rpc_v1_extra, ssh_docker_pipe, ssh_pipe, and tcp were updated to align with the new SSH architecture and improve test reliability.
  • A new test file tests/ssh/test_ssh_server.py was added to specifically test SshServer authentication methods.
  • A new stdio_rpc_server.py script was added to tests/scripts for stdio RPC testing.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Feb 8, 2026

Codecov Report

❌ Patch coverage is 61.34454% with 46 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.38%. Comparing base (bbda013) to head (3e95b3c).
⚠️ Report is 5 commits behind head on master.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
cbor_rpc/ssh/ssh_pipe.py 56.84% 38 Missing and 3 partials ⚠️
cbor_rpc/pipe/aio_pipe.py 44.44% 3 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master       #2      +/-   ##
==========================================
- Coverage   85.65%   83.38%   -2.28%     
==========================================
  Files          28       28              
  Lines        1220     1294      +74     
  Branches      126      137      +11     
==========================================
+ Hits         1045     1079      +34     
- Misses        133      170      +37     
- Partials       42       45       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant and valuable refactoring of the SSH functionality by creating a new SshServer class to manage connections, allowing for multiplexing commands over a single SSH connection. The changes also include extensive new tests and examples, which greatly improve the project's test coverage and usability. However, I've identified a few critical issues, including a syntax error and incorrect API usage in the new examples, as well as some areas for improvement in error handling and event naming. Addressing these points will help ensure the new features are robust and easy to use.

Comment thread cbor_rpc/ssh/ssh_pipe.py Outdated
Comment on lines +72 to +79
if isinstance(self._ssh_channel, asyncssh.SSHClientChannel):
cl_chan: asyncssh.SSHClientChannel = self._ssh_channel
print("Force aborting SSH channel...")
cl_chan.abort()
elif isinstance(self._ssh_channel, asyncssh.SSHClientProcess):
print("Force killing SSH process...")
cl_proc: asyncssh.SSHClientProcess = self._ssh_channel
cl_proc.kill()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This block of code has an extra level of indentation, which will cause a SyntaxError. It should be aligned with the except asyncio.TimeoutError: block.

Suggested change
if isinstance(self._ssh_channel, asyncssh.SSHClientChannel):
cl_chan: asyncssh.SSHClientChannel = self._ssh_channel
print("Force aborting SSH channel...")
cl_chan.abort()
elif isinstance(self._ssh_channel, asyncssh.SSHClientProcess):
print("Force killing SSH process...")
cl_proc: asyncssh.SSHClientProcess = self._ssh_channel
cl_proc.kill()
if isinstance(self._ssh_channel, asyncssh.SSHClientChannel):
cl_chan: asyncssh.SSHClientChannel = self._ssh_channel
print("Force aborting SSH channel...")
cl_chan.abort()
elif isinstance(self._ssh_channel, asyncssh.SSHClientProcess):
print("Force killing SSH process...")
cl_proc: asyncssh.SSHClientProcess = self._ssh_channel
cl_proc.kill()

Comment thread examples/ssh_exec/rpc_client.py Outdated
Comment on lines +23 to +29
server = SshServer(
host=args.host,
port=args.port,
username=args.user,
password=args.password,
)
await server.connect()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The SshServer is being instantiated incorrectly. The SshServer.connect() is a class method that should be awaited to get a connected server instance. The current code attempts to instantiate SshServer directly without a connection object, which will fail, and then calls a non-existent instance method connect().

Suggested change
server = SshServer(
host=args.host,
port=args.port,
username=args.user,
password=args.password,
)
await server.connect()
server = await SshServer.connect(
host=args.host,
port=args.port,
username=args.user,
password=args.password,
)

Comment thread examples/ssh_exec/simple_exec.py Outdated
Comment on lines +18 to +29
server = SshServer(
host=args.host,
port=args.port,
username=args.user,
password=args.password,
)

try:
await server.connect()
except Exception as e:
print(f"Failed to connect: {e}")
return
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The SshServer is being instantiated and used incorrectly. SshServer.connect() is an async class method that should be awaited to create and connect the server instance. The current code will raise an error.

Suggested change
server = SshServer(
host=args.host,
port=args.port,
username=args.user,
password=args.password,
)
try:
await server.connect()
except Exception as e:
print(f"Failed to connect: {e}")
return
try:
server = await SshServer.connect(
host=args.host,
port=args.port,
username=args.user,
password=args.password,
)
except Exception as e:
print(f"Failed to connect: {e}")
return

Comment thread cbor_rpc/ssh/ssh_pipe.py
Comment thread conftest.py Outdated
return host
return "localhost"

@pytest.fixture(scope="session")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There seems to be a duplication of SSH test fixtures between this file (root conftest.py) and tests/conftest.py. The fixtures in tests/conftest.py seem to follow a better pattern (e.g., separating config from the connected server instance) and are likely the intended ones. To avoid confusion and maintain a single source of truth for test setup, it would be best to consolidate these fixtures into tests/conftest.py and remove the duplicated ones from this file.

Comment thread cbor_rpc/pipe/aio_pipe.py Outdated
Comment on lines +188 to +189
except Exception:
pass
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Catching a broad Exception and silently passing can hide potential issues during the drain operation. It would be beneficial to log or print the exception to aid in debugging, even if the program flow can continue.

Suggested change
except Exception:
pass
except Exception as e:
print(f"AioPipe: Failed to drain writer on EOF: {e}")

Comment thread cbor_rpc/ssh/ssh_pipe.py Outdated
Comment on lines +61 to +79
print("Sent EOF to SSH channel, waiting for it to close...")

print("Terminating SSH channel...")
self._ssh_channel.terminate()
try:
# Wait "for" graceful closure for a short time
print("waiting")
await asyncio.wait_for(self._ssh_channel.wait_closed(), timeout=4.0)
except asyncio.TimeoutError:
# If timed out, try to force terminate

if isinstance(self._ssh_channel, asyncssh.SSHClientChannel):
cl_chan: asyncssh.SSHClientChannel = self._ssh_channel
print("Force aborting SSH channel...")
cl_chan.abort()
elif isinstance(self._ssh_channel, asyncssh.SSHClientProcess):
print("Force killing SSH process...")
cl_proc: asyncssh.SSHClientProcess = self._ssh_channel
cl_proc.kill()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The terminate method contains several print statements for debugging. For production-quality code, these should be replaced with a proper logging mechanism (e.g., using the logging module). This allows for better control over log levels and output destinations.

Comment thread cbor_rpc/ssh/ssh_pipe.py
Comment on lines +83 to +84
if self._ssh_channel:
await self._ssh_channel.wait_closed()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The call to self._ssh_channel.wait_closed() is redundant as it's already called on the preceding line. This block can be removed.

Comment thread cbor_rpc/ssh/ssh_pipe.py Outdated
Comment on lines +115 to +121
keys = client_keys
if ssh_key_content:
key = asyncssh.import_private_key(ssh_key_content, passphrase=ssh_key_passphrase)
if keys:
keys.append(key)
else:
keys = [key]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The client_keys list is modified in-place if it's provided. This can lead to unexpected side effects for the caller. It's safer to create a copy of the list before appending to it.

        keys = list(client_keys) if client_keys else []
        if ssh_key_content:
            key = asyncssh.import_private_key(ssh_key_content, passphrase=ssh_key_passphrase)
            keys.append(key)

@mesudip mesudip merged commit 75842b0 into master Feb 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants