Skip to content

infra(test): protocol-aware loopback peers + error injection (Phase 2 of #1060) #1074

Description

@kcenon

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 (HEADERSDATA(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

  • All five phases (2A-2E) merged to develop
  • Each peer documented in tests/support/README.md with a copy-paste composition example
  • CI green on every phase PR
  • At least one previously-impossible test pattern demonstrated per protocol family
  • No public-API or src/ behavioral changes (verified by reviewer)

Non-goals (explicitly)

Related

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions