Skip to content

feat: full test coverage on http-client#160

Closed
viraatc wants to merge 1 commit intomainfrom
feat/viraatc-improve-coverage
Closed

feat: full test coverage on http-client#160
viraatc wants to merge 1 commit intomainfrom
feat/viraatc-improve-coverage

Conversation

@viraatc
Copy link
Copy Markdown
Collaborator

@viraatc viraatc commented Mar 9, 2026

What does this PR do?

Type of change

  • Bug fix
  • New feature
  • Documentation update
  • Refactor/cleanup

Related issues

Testing

  • Tests added/updated
  • All tests pass locally
  • Manual testing completed

Checklist

  • Code follows project style
  • Pre-commit hooks pass
  • Documentation updated (if needed)

@viraatc viraatc requested a review from a team as a code owner March 9, 2026 23:18
Copilot AI review requested due to automatic review settings March 9, 2026 23:18
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 9, 2026

MLCommons CLA bot All contributors have signed the MLCommons CLA ✍️ ✅

@github-actions github-actions bot requested review from arekay-nv and nvzhihanj March 9, 2026 23:18
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, 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 significantly enhances the test coverage and robustness of the HTTP client and worker components. The changes introduce a wide array of new unit and integration tests, ensuring that various edge cases, error conditions, and lifecycle events are handled correctly. This refactoring effort aims to improve the reliability and maintainability of the client's asynchronous operations and connection management.

Highlights

  • Enhanced HTTPClient Test Coverage: Added new test cases for HTTPEndpointClient covering initialization scenarios (missing ZMQ context, external event loop usage) and improved shutdown behavior, including logging dropped requests.
  • Comprehensive Worker Module Testing: Introduced extensive new tests for the Worker module, addressing various configuration options (warmup modes, API keys), HTTPS SSL context creation, garbage collection modes, event recording for streaming and non-streaming requests, and robust error handling for HTTP 500 responses. Also, added tests for SSE boundary handling, graceful cleanup with pending tasks, and behavior during shutdown.
  • Improved ZMQ Transport Robustness Tests: Implemented detailed tests for _ZmqReceiverTransport and _ZmqSenderTransport to verify correct close and shutdown paths, error handling for ZMQ exceptions (including zmq.Again and unexpected errno values), and proper buffer management. New tests also cover ZmqWorkerPoolTransport creation edge cases like Windows platform rejection and long IPC paths.
  • Detailed HTTP Protocol and Connection Pool Tests: Expanded test coverage for HttpResponseProtocol to ensure correct handling of connection loss scenarios (before/after body complete, with pending futures), parser errors from malformed data, and efficient header/body reading. The ConnectionPool received new tests for stale connection discarding, idle timeout, connection warmup, waiter cancellation during pool closure, and idempotency of connection release.
  • Increased Test Timeouts: Adjusted wait_for_workers_ready timeouts in test_worker.py from 0.5 seconds to 5 seconds to provide more stability for integration tests involving worker readiness.

🧠 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
  • tests/integration/endpoint_client/test_http_client.py
    • Added TestInit class to test HTTPEndpointClient initialization with and without zmq_context and external event loops.
    • Added test_shutdown_logs_dropped_requests to verify logging behavior during client shutdown.
  • tests/integration/endpoint_client/test_worker.py
    • Imported tempfile and Path for new test cases.
    • Added test_set_start_method_already_set to verify multiprocessing start method handling.
    • Increased pool.wait_for_workers_ready timeout from 0.5s to 5s in multiple test functions.
    • Added TestWorkerEdgeCases class with tests for worker config variants, HTTPS SSL context, GC modes, event recording, HTTP 500 error propagation, SSE boundary handling, cleanup with pending tasks, requests during shutdown, and various warmup failure scenarios.
    • Added tests for worker event recording with errors and when warmed connections are below minimum required.
    • Added tests for Worker.run() exception handling and main loop error continuation.
    • Added test_worker_fire_request_shutdown_early_return to verify graceful handling of requests during shutdown.
  • tests/unit/async_utils/transport/test_zmq.py
    • Imported errno, patch, and zmq for new test cases.
    • Added TestReceiverCloseBehavior with tests for _ZmqReceiverTransport's recv() behavior when closed, _on_readable early return, close() canceling pending calls, and handling remove_reader errors.
    • Added TestReceiverErrorHandling with tests for _on_readable logging ZMQError, ignoring specific ZMQError errno values, and handling ZMQError during reschedule.
    • Added TestSenderBehavior with tests for _ZmqSenderTransport's _on_writable behavior when closing, draining buffer, handling zmq.Again, clearing buffer on unexpected ZMQError, and rescheduling.
    • Added tests for _ZmqSenderTransport's _stop_writing handling remove_writer errors, close() canceling pending callbacks, and send() handling ZMQError on fast-path.
    • Added TestZmqWorkerPoolTransportCreation with tests for ZmqWorkerPoolTransport.create() rejecting Windows and long IPC paths, wait_for_workers_ready with no timeout, and __del__ calling cleanup and suppressing exceptions.
  • tests/unit/endpoint_client/test_http.py
    • Added assertion for protocol.transport not being None in test_parses_200_response_with_body.
    • Added test_connection_lost_before_body_complete and test_connection_lost_after_message_complete to verify error propagation and normal resolution for connection_lost.
    • Added test_connection_lost_message_complete_body_future_pending to ensure body_future resolves correctly.
    • Added test_data_received_parser_error and test_data_received_parser_error_with_body_future for httptools.HttpParserError propagation.
    • Added test_read_body_completes_on_message_complete and test_read_headers_fast_path for efficient body/header reading.
    • Added test_stale_connection_discarded_on_acquire to verify connection pool behavior with stale connections.
    • Added test_idle_timeout_discards_connection for connection pool idle timeout functionality.
    • Added test_warmup_creates_connections and test_warmup_exceeding_max_raises for connection pool warmup behavior.
    • Added test_pool_close_cancels_waiters to ensure proper cleanup of pending acquire tasks.
    • Added test_release_idempotent for verifying connection release idempotency.
    • Added test_unlimited_pool for connection pools with no maximum connection limit.
    • Added test_waiter_creates_new_connection for scenarios where waiters create new connections.
    • Added test_is_stale_various_conditions for comprehensive testing of connection staleness checks.
Activity
  • The pull request is marked as a refactor/cleanup, indicating a focus on improving existing code rather than adding new user-facing features.
  • New tests have been added and existing tests updated, confirming a significant investment in test coverage.
  • All local tests are reported to be passing, and manual testing has been completed.
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.

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 significantly improves the test coverage for the HTTP client and its underlying components. The new tests are very thorough, covering a wide range of functionality, edge cases, and error conditions for the ZMQ transport, HTTP protocol parsing, connection pooling, and worker lifecycle. This greatly increases confidence in the robustness of the client. I have one suggestion to improve the maintainability of a unit test, but overall this is an excellent contribution.

Comment on lines +756 to +807
async def test_is_stale_various_conditions(self, pool):
"""is_stale() returns correct results for different connection states."""
import time as time_mod

conn = await pool.acquire()
pool.release(conn)

# 1. Recently used — fast path returns False
assert not conn.is_stale()

# 2. Age past 1s, healthy connection — not stale
conn.last_used = time_mod.monotonic() - 2.0
assert not conn.is_stale()

# 3. Transport is None — stale
saved_transport = conn.transport
conn.transport = None # type: ignore[assignment]
conn.last_used = time_mod.monotonic() - 2.0
assert conn.is_stale()
conn.transport = saved_transport # restore

# 4. get_extra_info("socket") returns None — stale
conn.transport = saved_transport # ensure transport is restored
orig_get_extra = conn.transport.get_extra_info
conn.transport.get_extra_info = lambda name, default=None: None # type: ignore[assignment]
conn.last_used = time_mod.monotonic() - 2.0
assert conn.is_stale()
conn.transport.get_extra_info = orig_get_extra # type: ignore[assignment]

# 5. fd < 0 — stale
from unittest.mock import MagicMock

mock_sock = MagicMock()
mock_sock.fileno.return_value = -1
conn.transport = saved_transport
conn.transport.get_extra_info = (
lambda name, default=None: mock_sock if name == "socket" else default
) # type: ignore[assignment]
conn.last_used = time_mod.monotonic() - 2.0
assert conn.is_stale()

# 6. OSError from fileno/select — stale
mock_sock2 = MagicMock()
mock_sock2.fileno.side_effect = OSError("bad fd")
conn.transport.get_extra_info = (
lambda name, default=None: mock_sock2 if name == "socket" else default
) # type: ignore[assignment]
conn.last_used = time_mod.monotonic() - 2.0
assert conn.is_stale()

conn.transport.get_extra_info = orig_get_extra # type: ignore[assignment]

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

This test is very thorough in covering the different conditions for a stale connection. However, the implementation relies on manually saving and restoring mocked methods (get_extra_info) and object attributes (transport). This can be fragile, as an assertion failure could leave the object in a modified state, potentially affecting subsequent tests if fixtures were shared.

A more robust and readable approach would be to use unittest.mock.patch.object as a context manager for each condition being tested. This ensures that mocks are automatically cleaned up, even if assertions fail.

Here's an example of how case #4 could be refactored:

from unittest.mock import patch

# ... inside the test ...

# 4. get_extra_info("socket") returns None — stale
conn.last_used = time_mod.monotonic() - 2.0
with patch.object(conn.transport, 'get_extra_info', return_value=None):
    assert conn.is_stale()
# Original method is restored automatically

Applying this pattern to all mock-based checks in this test would improve its maintainability.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR expands the test suite around the http-client stack (HTTP parsing/pooling, ZMQ transport, worker lifecycle, and endpoint client initialization/shutdown) to move toward full coverage.

Changes:

  • Add unit tests for HttpResponseProtocol and ConnectionPool edge/error paths.
  • Add extensive unit tests for ZMQ transport sender/receiver close and error-handling branches.
  • Add/extend integration tests for worker behaviors (config variants, SSE boundaries, shutdown races) and http-client initialization/shutdown.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
tests/unit/endpoint_client/test_http.py Adds unit/integration tests covering HTTP protocol parsing, connection loss, and pool behaviors (warmup, stale/idle timeout, waiters).
tests/unit/async_utils/transport/test_zmq.py Adds unit tests for ZMQ transport close paths, sender/receiver error handling, and factory/lifecycle behaviors.
tests/integration/endpoint_client/test_worker.py Adds many worker integration tests for config variants, SSE boundaries, shutdown edge cases, warmup failure handling, and GC modes.
tests/integration/endpoint_client/test_http_client.py Adds tests for missing ZMQ context, external loop usage, and shutdown behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

endpoint_urls=[f"http://localhost:{server.port}/error"],
num_workers=1,
max_connections=10,
warmup_connections=False,
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

HTTPClientConfig.warmup_connections is declared as an int (0 = disabled, -1 = auto). Passing False works only because bool is a subclass of int, but it's ambiguous and can confuse readers/type-checkers. Use warmup_connections=0 here to explicitly disable warmup.

Suggested change
warmup_connections=False,
warmup_connections=0,

Copilot uses AI. Check for mistakes.
endpoint_urls=[f"http://localhost:{server.port}/sse-test"],
num_workers=1,
max_connections=10,
warmup_connections=False,
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

HTTPClientConfig.warmup_connections is an int (0 disables warmup). Using False relies on bool being an int and is unclear; please change this to warmup_connections=0 for explicitness.

Suggested change
warmup_connections=False,
warmup_connections=0,

Copilot uses AI. Check for mistakes.
endpoint_urls=[f"http://localhost:{server.port}/slow"],
num_workers=1,
max_connections=10,
warmup_connections=False,
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

HTTPClientConfig.warmup_connections is typed/defined as an int (0 disables warmup). Using False here is ambiguous and depends on bool subclassing int; please switch to warmup_connections=0.

Suggested change
warmup_connections=False,
warmup_connections=0,

Copilot uses AI. Check for mistakes.
Comment on lines +632 to +640
def test_shutdown_logs_dropped_requests(self, mock_http_echo_server):
"""Shutdown logs count of dropped requests when > 0."""
with ManagedZMQContext.scoped() as zmq_ctx:
client = _create_client(
f"{mock_http_echo_server.url}/v1/chat/completions",
zmq_context=zmq_ctx,
)
client._dropped_requests = 3
client.shutdown()
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

This test is named/documented as verifying that shutdown logs dropped requests, but it doesn't assert on any emitted log output (no caplog/logger patching). As a result it will pass even if nothing is logged. Please capture logs and assert the expected message/level when _dropped_requests > 0, or rename the test to reflect what it actually verifies.

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +44
"""set_start_method handles already-set case.

The module-level code catches RuntimeError when the
multiprocessing start method is already set. In test environments, it
is always already set (by pytest or another import). We verify the
module imported successfully, which means the except block executed.
"""
import multiprocessing

from inference_endpoint.endpoint_client import worker

assert worker is not None
# The start method should be set (either by the module or by pytest)
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

The docstring claims the RuntimeError "already set" path was exercised ("which means the except block executed"), but this test never forces multiprocessing.set_start_method(...) to raise. As written it only validates that the module imports and that get_start_method() is non-None, which doesn't prove the exception-handling path ran. Please either update the docstring/test name to match what is asserted, or restructure the test to deterministically hit the RuntimeError-handling branch without impacting other tests.

Suggested change
"""set_start_method handles already-set case.
The module-level code catches RuntimeError when the
multiprocessing start method is already set. In test environments, it
is always already set (by pytest or another import). We verify the
module imported successfully, which means the except block executed.
"""
import multiprocessing
from inference_endpoint.endpoint_client import worker
assert worker is not None
# The start method should be set (either by the module or by pytest)
"""Worker module import succeeds and multiprocessing start method is configured.
The worker module sets or validates the multiprocessing start method
at import time. This test ensures that importing the module does not
raise and that a start method is configured in the current environment.
It does not assert which internal code path (initial set vs
already-set/RuntimeError handling) was taken.
"""
import multiprocessing
from inference_endpoint.endpoint_client import worker
assert worker is not None
# The start method should be set (either by the module or by the test environment)

Copilot uses AI. Check for mistakes.
pool = ZmqWorkerPoolTransport.create(event_loop, 1, zmq_ctx)

# Call __del__ directly -- should not raise
pool.__del__()
pool.cleanup = raising_cleanup

# __del__ should not raise
pool.__del__()
async def test_pool_close_cancels_waiters(self, pool):
"""close() cancels all pending waiters and clears the waiter queue."""
# Acquire all 4 connections to saturate the pool
_conns = [await pool.acquire() for _ in range(4)]
protocol.connection_lost(exc)

with pytest.raises(expected_error):
await body_task
protocol.data_received(b"NOT HTTP AT ALL")

with pytest.raises(httptools.HttpParserError):
await headers_task
# Feed invalid chunk framing — triggers HttpParserError on body_future
protocol.data_received(b"ZZZZ\r\nNOT A VALID CHUNK\r\n")
with pytest.raises(httptools.HttpParserError):
await body_task
result = await asyncio.wait_for(receiver.recv(), timeout=1.0)
assert result is None

await close_task
# Remove the reader before close, so close() will get an error
try:
loop.remove_reader(receiver._fd)
except (ValueError, OSError):
@viraatc viraatc closed this Mar 10, 2026
@github-actions github-actions bot locked and limited conversation to collaborators Mar 10, 2026
@viraatc viraatc deleted the feat/viraatc-improve-coverage branch April 1, 2026 22:58
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants