Skip to content

infra: hermetic transport fixture for HTTP/2 / QUIC / WebSocket / gRPC unit tests #1060

Description

@kcenon

What

Build a hermetic transport fixture (mock TLS socket / mock UDP peer / mock WebSocket upgrade harness / gRPC dummy peer) that lets unit tests drive connect() / accept() / async_* callback chains on protocol clients and servers without standing up a real network listener.

Single deliverable in tests/support/:

tests/support/
  hermetic_transport_fixture.h
  hermetic_transport_fixture.cpp
  mock_tls_socket.h          // For HTTP/2 + gRPC client/server
  mock_udp_peer.h            // For QUIC socket/server
  mock_ws_handshake.h        // For WebSocket server

Why

Sub-issues #1048-#1053 added 4,383 LOC of hermetic gtest cases for the six lowest-coverage source files yet moved overall filtered coverage by +0.05pp line / +0.07pp branch — essentially zero. Per-file analysis after PR #1059 merge:

File Line% (before #1048) Line% (after #1059) Δ
protocols/http2/http2_client.cpp 18.8% 18.8% 0
protocols/grpc/client.cpp 22.6% 22.6% 0
protocols/http2/http2_server.cpp 38.3% 38.3% 0
internal/quic_socket.cpp 43.7% 52.2% +8.5
experimental/quic_server.cpp 43.7% 46.3% +2.6
http/websocket_server.cpp 39.9% 41.6% +1.7

Three files moved by exactly 0pp. The hermetic tests exercised inline-in-header code (config round-trips, callback registration, type aliases), not the .cpp body. Each PR's "Honest scope statement" named the same class of unreachable code: private async/socket-bound methods that demand a live transport stream.

Without a fixture, the remaining gap to epic #953's targets (Line ≥ 80%, Branch ≥ 70%) cannot be closed by adding more hermetic tests. This is a diminishing-returns wall, not a scale problem.

Who

  • Assignee: TBD
  • Stakeholders: anyone working on protocols/, experimental/, http/, internal/quic_socket.cpp
  • Reviewer: anyone with ASIO + OpenSSL test-infrastructure experience

When

Where

How

Technical approach

The structural blockers identified in PRs #1054-#1059 fall into three transport patterns:

  1. TLS-over-TCP frame stream (HTTP/2 client + server, gRPC client)

    • Need: drive process_frame, handle_*_frame, send_* private methods
    • Approach: inject a mock asio::ssl::stream<asio::ip::tcp::socket> substitute via templated transport, OR provide an in-memory asio::ip::tcp::socket pair connected via asio::local::stream_protocol (Unix domain sockets) with TLS layer mocked at the byte level
  2. UDP datagram + QUIC handshake (quic_socket, quic_server)

    • Need: drive do_receive, handle_packet, process_*_frame, find_or_create_session
    • Approach: in-process UDP peer running on a free local port, OR asio::posix::stream_descriptor-backed socketpair with QUIC initial-packet builder helpers
  3. HTTP/1.1 upgrade + WebSocket frame (websocket_server)

    • Need: drive do_accept, handle_new_connection, on_message, invoke_*_callback
    • Approach: in-process TCP client that performs the WS upgrade handshake against the listening server, then sends framed payloads

Acceptance criteria

  • hermetic_transport_fixture.h provides a WithMockTransport GTest fixture base
  • mock_tls_socket.h allows injecting raw bytes into HTTP/2 / gRPC client/server transport without an external peer
  • mock_udp_peer.h allows sending arbitrary QUIC initial/handshake packets to a quic_socket or quic_server instance under test
  • mock_ws_handshake.h simulates an RFC 6455 client upgrade and frame send to a websocket_server
  • At least one new TEST_F per fixture in each of the six existing branch_test files demonstrates the fixture exercising one previously-blocked private method
  • CI passes on Ubuntu/macOS Debug+Release matrix
  • Documentation: tests/support/README.md shows how to compose the fixture for new tests

Reproduction of structural blockers (from PRs #1054-#1059 honest-scope statements)

To validate the fixture, the new TEST_Fs MUST hit at least one method per category:

File Target method
http2_client.cpp process_frame (HEADERS frame dispatch)
grpc/client.cpp (TBD — likely send_message async chain)
http2_server.cpp handle_headers_frame
quic_socket.cpp process_crypto_frame (TLS handshake state machine)
quic_server.cpp find_or_create_session (lookup + create branch)
websocket_server.cpp handle_new_connection (WS upgrade complete)

Hitting one method per file is enough to prove the fixture works; subsequent PRs can ride on the same fixture to push per-file coverage upward.

Out of scope

Testing plan for reviewers

  1. Build with cmake --preset debug && cmake --build build
  2. Run ctest --test-dir build -R "branch_test" — all existing tests still pass
  3. Coverage delta check: cmake --build build --target coverage, verify per-file numbers for the six target files moved upward
  4. Run with --repeat-until-fail=20 -j4 to validate the fixture is race-free

Additional Context

This is the strategic complement to the per-file branch-coverage batch (#1048-#1053). The batch closed all narrow follow-ups that hermetic tests could close; the gap that remains is structural and requires this fixture.

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions