What
Phase 2 of #1060. Extend tests/support/ (the network::test_support static helper library shipped in #1060) so that the loopback peers actually speak each protocol's framing on top of the byte-level transport already in place. Today the fixture provides:
hermetic_transport_fixture — shared io_context + worker thread, make_loopback_tcp_pair(), make_loopback_udp_pair()
mock_tls_socket — RSA-2048 self-signed cert, server/client ssl::context, tls_loopback_listener (accepts one connection, no application framing)
mock_udp_peer — UDP wrapper + stub QUIC long-header Initial-packet builder
mock_ws_handshake — RFC 6455 client upgrade and frame builders
What is missing — and is the blocker for the six in-flight coverage sub-issues — is the server-side application framing that lets the client's post-connect code paths actually run. Examples:
http2_client::get/post/put/del/start_stream/write_stream all early-return through is_connected(). With the current tls_loopback_listener, the TLS handshake completes so is_connected() is true, but the next operation needs the peer to send the HTTP/2 SETTINGS preface and acknowledge stream HEADERS / DATA frames, which the listener never does. The methods therefore exit on the next state guard before reaching the code we want to cover.
grpc::client.cpp needs the same h2 framing plus gRPC length-prefix replies (HEADERS → DATA(grpc-frame) → HEADERS(trailers)).
quic_socket.cpp needs a peer that completes the Initial → Handshake → 1-RTT cryptographic state transitions, not just sends an Initial stub.
quic_server::handle_packet() and websocket_server::handle_new_connection() are private; tests/support/README.md (lines 92-96) acknowledges that these need a friend-test injection point to drive directly.
This issue ships those missing pieces.
Scope
Add to tests/support/ |
Purpose |
Drives coverage in |
mock_h2_server_peer.{h,cpp} |
Server-side HTTP/2 connection preface, SETTINGS / SETTINGS-ACK exchange, minimal HEADERS+DATA reply for one stream |
http2_client.cpp, http2_server.cpp |
mock_grpc_server_peer.{h,cpp} |
h2 + gRPC framing on top: receive application/grpc request, reply with :status 200 HEADERS, length-prefixed message DATA, trailers (grpc-status: 0) |
grpc/client.cpp |
mock_quic_peer_loop.{h,cpp} |
Long-header Initial echo + Handshake / 1-RTT key derivation stubs sufficient for the client's dispatch_packet() switch arms to run |
quic_socket.cpp, experimental/quic_server.cpp |
network_test_friends.h (in src/internal/... private headers, gated on BUILD_TESTING) |
friend class kcenon::network::tests::support::quic_server_probe; and equivalent for websocket_server::handle_new_connection() |
quic_server.cpp, websocket_server.cpp |
frame_injector.{h,cpp} |
Drop, truncate, malformed-frame, slow-write hooks composable with each peer |
branch coverage on error paths in all six target files |
Out of scope
Why
The six coverage sub-issues #1062, #1063, #1064, #1065, #1066, #1067 (children of #953 epic) all share one root cause: most uncovered code in their target files sits behind a connected protocol peer. The DISABLED_ConnectToHttpbin style integration tests reach external internet hosts and are unsuitable for CI. Each PR landed under those six issues honestly reported Part of #N rather than Closes #N because the >=80% line / >=70% branch acceptance criteria are unreachable without this infrastructure.
Without Phase 2:
With Phase 2:
- Each follow-up PR per sub-issue can drive private/post-connect methods directly, lift line coverage above 80% and branch coverage above 70%
- All six sub-issues become closeable
- A reusable substrate exists for any future protocol-level test in this repo
Where
tests/support/ — new files listed above; extend tests/support/CMakeLists.txt to compile and link them into network_test_support
tests/support/README.md — document new peers and friend-test injection points
src/internal/protocols/quic/quic_server.h — add friend declaration gated on BUILD_TESTING
src/internal/protocols/websocket/websocket_server.h — same
tests/CMakeLists.txt — already links network::test_support; no change expected
How
Phased delivery
Each phase is independently shippable as one PR. Total estimated 4-5 PRs.
| Phase |
Deliverable |
Acceptance |
| 2A |
mock_h2_server_peer + 1 demo TEST_F exercising one previously-disabled path in http2_client.cpp |
Demo test passes; http2_client.cpp line coverage strictly increases |
| 2B |
mock_grpc_server_peer + 1 demo TEST_F for grpc/client.cpp |
Same shape |
| 2C |
mock_quic_peer_loop + 1 demo TEST_F for quic_socket.cpp |
Same shape |
| 2D |
network_test_friends.h injection + 1 demo TEST_F each for quic_server and websocket_server |
handle_packet / handle_new_connection invoked directly by a test |
| 2E |
frame_injector + 1 demo TEST_F per protocol showing one error branch driven (drop, truncate, malformed, or slow-write) |
Branch coverage on the targeted error path strictly increases |
Per skill rules each phase PR stays in S/M size band. Coverage expansion to >=80% / >=70% remains the responsibility of the existing follow-up sub-issues, which become unblocked phase by phase.
Acceptance criteria for closing this issue
Non-goals (explicitly)
Related
What
Phase 2 of #1060. Extend
tests/support/(thenetwork::test_supportstatic helper library shipped in #1060) so that the loopback peers actually speak each protocol's framing on top of the byte-level transport already in place. Today the fixture provides:hermetic_transport_fixture— sharedio_context+ worker thread,make_loopback_tcp_pair(),make_loopback_udp_pair()mock_tls_socket— RSA-2048 self-signed cert, server/clientssl::context,tls_loopback_listener(accepts one connection, no application framing)mock_udp_peer— UDP wrapper + stub QUIC long-header Initial-packet buildermock_ws_handshake— RFC 6455 client upgrade and frame buildersWhat is missing — and is the blocker for the six in-flight coverage sub-issues — is the server-side application framing that lets the client's post-connect code paths actually run. Examples:
http2_client::get/post/put/del/start_stream/write_streamall early-return throughis_connected(). With the currenttls_loopback_listener, the TLS handshake completes sois_connected()istrue, but the next operation needs the peer to send the HTTP/2 SETTINGS preface and acknowledge stream HEADERS / DATA frames, which the listener never does. The methods therefore exit on the next state guard before reaching the code we want to cover.grpc::client.cppneeds the same h2 framing plus gRPC length-prefix replies (HEADERS→DATA(grpc-frame)→HEADERS(trailers)).quic_socket.cppneeds a peer that completes the Initial → Handshake → 1-RTT cryptographic state transitions, not just sends an Initial stub.quic_server::handle_packet()andwebsocket_server::handle_new_connection()are private;tests/support/README.md(lines 92-96) acknowledges that these need a friend-test injection point to drive directly.This issue ships those missing pieces.
Scope
tests/support/mock_h2_server_peer.{h,cpp}http2_client.cpp,http2_server.cppmock_grpc_server_peer.{h,cpp}application/grpcrequest, reply with:status 200HEADERS, length-prefixed message DATA, trailers (grpc-status: 0)grpc/client.cppmock_quic_peer_loop.{h,cpp}dispatch_packet()switch arms to runquic_socket.cpp,experimental/quic_server.cppnetwork_test_friends.h(insrc/internal/...private headers, gated onBUILD_TESTING)friend class kcenon::network::tests::support::quic_server_probe;and equivalent forwebsocket_server::handle_new_connection()quic_server.cpp,websocket_server.cppframe_injector.{h,cpp}Out of scope
src/Why
The six coverage sub-issues #1062, #1063, #1064, #1065, #1066, #1067 (children of #953 epic) all share one root cause: most uncovered code in their target files sits behind a connected protocol peer. The
DISABLED_ConnectToHttpbinstyle integration tests reach external internet hosts and are unsuitable for CI. Each PR landed under those six issues honestly reportedPart of #Nrather thanCloses #Nbecause the >=80% line / >=70% branch acceptance criteria are unreachable without this infrastructure.Without Phase 2:
With Phase 2:
Where
tests/support/— new files listed above; extendtests/support/CMakeLists.txtto compile and link them intonetwork_test_supporttests/support/README.md— document new peers and friend-test injection pointssrc/internal/protocols/quic/quic_server.h— add friend declaration gated onBUILD_TESTINGsrc/internal/protocols/websocket/websocket_server.h— sametests/CMakeLists.txt— already linksnetwork::test_support; no change expectedHow
Phased delivery
Each phase is independently shippable as one PR. Total estimated 4-5 PRs.
mock_h2_server_peer+ 1 demoTEST_Fexercising one previously-disabled path inhttp2_client.cpphttp2_client.cppline coverage strictly increasesmock_grpc_server_peer+ 1 demoTEST_Fforgrpc/client.cppmock_quic_peer_loop+ 1 demoTEST_Fforquic_socket.cppnetwork_test_friends.hinjection + 1 demoTEST_Feach forquic_serverandwebsocket_serverhandle_packet/handle_new_connectioninvoked directly by a testframe_injector+ 1 demoTEST_Fper protocol showing one error branch driven (drop, truncate, malformed, or slow-write)Per skill rules each phase PR stays in S/M size band. Coverage expansion to >=80% / >=70% remains the responsibility of the existing follow-up sub-issues, which become unblocked phase by phase.
Acceptance criteria for closing this issue
developtests/support/README.mdwith a copy-paste composition examplesrc/behavioral changes (verified by reviewer)Non-goals (explicitly)
127.0.0.1:0(kernel-assigned ephemeral port) per the hermetic discipline already established in infra: hermetic transport fixture for HTTP/2 / QUIC / WebSocket / gRPC unit tests #1060.Related