Skip to content

test(quic): expand quic_socket coverage with mock_udp_peer (Part of #1065)#1079

Merged
kcenon merged 1 commit into
developfrom
test/issue-1065-quic-socket-mock-peer-coverage
Apr 28, 2026
Merged

test(quic): expand quic_socket coverage with mock_udp_peer (Part of #1065)#1079
kcenon merged 1 commit into
developfrom
test/issue-1065-quic-socket-mock-peer-coverage

Conversation

@kcenon

@kcenon kcenon commented Apr 28, 2026

Copy link
Copy Markdown
Owner

What

Adds 8 new TEST_F cases to tests/unit/quic_socket_branch_test.cpp (+299 LOC, no new test files) under the existing QuicSocketHermeticTransportTest fixture. They compose the existing make_loopback_udp_pair + mock_udp_peer infrastructure (introduced in #1060) to drive quic_socket paths that were left uncovered by the PR #1071 batch, which only exercised idle/disconnected-state validation.

Newly reached paths in src/internal/quic_socket.cpp

  • connect() success path past TLS init, initial-secret derivation, ClientHello generation, queue_crypto_data, send_pending_packets, and send_packet at the initial encryption level (lines 135-211).
  • connect() state transition idle -> handshake_start -> handshake observed via state().
  • close() from non-handshake-complete state: builds a CONNECTION_CLOSE frame and dispatches it as a second datagram on the same socket (lines 251-298).
  • do_receive() lambda forwarding a real loopback datagram to handle_packet(), including:
    • The silent-drop branch when packet_parser::parse_header fails on a 1-byte input.
    • The silent-drop branch when get_read_keys() reports keys-not-yet-derived on a long-header Initial stub against a fresh client.
  • stop_receive() correctness under an active peer-side sender (the is_receiving_ short-circuit in the receive completion lambda).
  • Distinct connection-id generation across two independent client sockets, verified by extracting the SCID from each captured Initial packet.
  • connect() with an explicit non-empty SNI string still emits a long-header Initial packet.

Why

Part of #1065 and Part of #953 (epic: 40% -> 80% coverage).

src/internal/quic_socket.cpp line coverage stood at 43.7% line / 21.3% branch as of the 2026-04-26 lcov run (workflow 24947193873). PR #1071 added 35 TEST_F cases (583 LOC) covering validation guards, state-machine guards on send_stream_data/create_stream/close_stream, receive-loop idempotency, multi-instance state isolation, move semantics, and callback edge cases — but every one of those tests left the socket in idle state and never put a datagram on the loopback wire. As a result, large blocks of the file (the connect/close packet emit chain, the receive-completion lambda, handle_packet's parse and key-availability branches) were not driven by any unit test.

Because make_loopback_udp_pair (from hermetic_transport_fixture.h) and mock_udp_peer (from mock_udp_peer.h) already ship as part of network::test_support (linked at tests/CMakeLists.txt:4962), this PR can drive those paths without introducing any new test infrastructure. The HEADERS+DATA-style server replies and the connected_callback completion path remain gated on Phase 2C of #1074 (mock_quic_peer_loop with TLS 1.3 key derivation), so this PR uses Part of #1065, not Closes.

Where

Path Change
tests/unit/quic_socket_branch_test.cpp +299 LOC: 8 new TEST_F cases appended under QuicSocketHermeticTransportTest, plus <array> and <span> includes added explicitly (previously transitively included via mock_udp_peer.h).

No changes under src/, no new test files, no CMake change, no new test_support files.

How

Each new TEST_F:

  1. Constructs a make_loopback_udp_pair(io()) from the inherited hermetic_transport_fixture worker thread.
  2. Wraps one half in mock_udp_peer, takes its endpoint, and uses the other half to construct a quic_socket (client or server role).
  3. Drives the targeted path:
    • For connect-driven tests: calls client->connect(peer_endpoint, ...), then peer.receive(4096, 200ms) to capture the emitted Initial datagram.
    • For do_receive-driven tests: registers an error_callback that increments an atomic counter, calls start_receive(), sends bytes from the peer side, sleeps 50ms, calls stop_receive(), and asserts the counter remained at zero (silent-drop branches do not invoke error_callback).
    • For stop_receive-correctness: arms start_receive() then immediately stop_receive() before the peer send, asserting the datagram never reaches handle_packet.
  4. Tears down via stop_receive() and shared_ptr destruction.

Tests reaching the connect-side paths assert the captured first byte has the long-header high+fixed bits (>= 0xC0) per RFC 9000 Section 17.2, which is stronger than just "something was sent" — it confirms the emitted bytes are shaped like a QUIC long-header packet rather than arbitrary garbage.

The MultipleConcurrentConnectsHaveDistinctSourceConnectionIds test parses the long-header SCID by walking the byte layout (byte0 + version[4] + dcid_len[1] + dcid + scid_len[1] + scid) and asserts the two extracted SCIDs differ, validating that generate_connection_id() produces independent IDs per fresh socket.

Coverage Scope (Honest Assessment)

Local C++ toolchain (cmake/g++/ninja) is not available in this dev environment; the coverage.yml workflow is the authoritative verification surface (PR #1071 / #1075 / #1076 / #1077 precedent). Expected outcome:

Part of #1065
Part of #953

Add 8 new TEST_F cases under the existing QuicSocketHermeticTransportTest
fixture in tests/unit/quic_socket_branch_test.cpp. They use
make_loopback_udp_pair + mock_udp_peer to exercise paths that PR #1071
left uncovered because that PR only validated idle/disconnected state.

New paths driven:

- connect() success path past TLS init / derive_initial_secrets /
  start_handshake / queue_crypto_data / send_pending_packets /
  send_packet, with the loopback peer capturing the long-header
  Initial datagram and verifying the 0xC0+ first byte and a parsed
  src connection id.
- connect() state transition from idle to handshake_start/handshake.
- close() at the initial encryption level building a CONNECTION_CLOSE
  frame and dispatching it as a second datagram on the same socket.
- do_receive() lambda handing a real datagram to handle_packet, with
  the silent-drop branches taken when packet_parser::parse_header
  fails (1-byte input) or when get_read_keys reports keys-not-yet-
  derived (long-header Initial stub on a fresh client).
- stop_receive() correctness under an active peer-side sender.
- Distinct connection-id generation across two independent client
  sockets, asserted via SCID extraction from the captured Initial
  packets.
- connect() with an explicit non-empty SNI string still emitting a
  long-header Initial.

No new test_support files; no CMake changes (the file is already
linked to network::test_support at tests/CMakeLists.txt:4962).

The acceptance criteria of issue #1065 (>= 80% line / >= 70% branch
on src/internal/quic_socket.cpp) remain unreachable until Phase 2C
of #1074 lands mock_quic_peer_loop with TLS 1.3 key derivation, so
this PR ships incremental progress only.

Part of #1065
Part of #953
@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report

Metric Value
Line Coverage 66.8%
Branch Coverage 33.1%
Target 80% lines / 70% branches
Coverage Details

Full HTML report is available as a build artifact.

@kcenon kcenon merged commit c06639d into develop Apr 28, 2026
9 checks passed
@kcenon kcenon deleted the test/issue-1065-quic-socket-mock-peer-coverage branch April 28, 2026 22:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant