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:
-
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
-
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
-
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
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
- Build with
cmake --preset debug && cmake --build build
- Run
ctest --test-dir build -R "branch_test" — all existing tests still pass
- Coverage delta check:
cmake --build build --target coverage, verify per-file numbers for the six target files moved upward
- 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.
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/: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:
protocols/http2/http2_client.cppprotocols/grpc/client.cppprotocols/http2/http2_server.cppinternal/quic_socket.cppexperimental/quic_server.cpphttp/websocket_server.cppThree files moved by exactly 0pp. The hermetic tests exercised inline-in-header code (config round-trips, callback registration, type aliases), not the
.cppbody. 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
protocols/,experimental/,http/,internal/quic_socket.cppWhen
Where
tests/support/(new directory)tests/unit/{http2_client,grpc_client,http2_server,quic_socket,quic_server,websocket_server}_branch_test.cpp— extended with newTEST_Fcases that use the fixturetests/CMakeLists.txt— link the fixture as a static helper library, available to alladd_network_testtargetsHow
Technical approach
The structural blockers identified in PRs #1054-#1059 fall into three transport patterns:
TLS-over-TCP frame stream (HTTP/2 client + server, gRPC client)
process_frame,handle_*_frame,send_*private methodsasio::ssl::stream<asio::ip::tcp::socket>substitute via templated transport, OR provide an in-memoryasio::ip::tcp::socketpair connected viaasio::local::stream_protocol(Unix domain sockets) with TLS layer mocked at the byte levelUDP datagram + QUIC handshake (quic_socket, quic_server)
do_receive,handle_packet,process_*_frame,find_or_create_sessionasio::posix::stream_descriptor-backed socketpair with QUIC initial-packet builder helpersHTTP/1.1 upgrade + WebSocket frame (websocket_server)
do_accept,handle_new_connection,on_message,invoke_*_callbackAcceptance criteria
hermetic_transport_fixture.hprovides aWithMockTransportGTest fixture basemock_tls_socket.hallows injecting raw bytes into HTTP/2 / gRPC client/server transport without an external peermock_udp_peer.hallows sending arbitrary QUIC initial/handshake packets to aquic_socketorquic_serverinstance under testmock_ws_handshake.hsimulates an RFC 6455 client upgrade and frame send to awebsocket_servertests/support/README.mdshows how to compose the fixture for new testsReproduction 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:
http2_client.cppprocess_frame(HEADERS frame dispatch)grpc/client.cppsend_messageasync chain)http2_server.cpphandle_headers_framequic_socket.cppprocess_crypto_frame(TLS handshake state machine)quic_server.cppfind_or_create_session(lookup + create branch)websocket_server.cpphandle_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
cmake --preset debug && cmake --build buildctest --test-dir build -R "branch_test"— all existing tests still passcmake --build build --target coverage, verify per-file numbers for the six target files moved upward--repeat-until-fail=20 -j4to validate the fixture is race-freeAdditional 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.