Skip to content

test(quic): expand branch coverage for quic_socket.cpp#1057

Merged
kcenon merged 1 commit into
developfrom
test/issue-1051-quic-socket-coverage
Apr 26, 2026
Merged

test(quic): expand branch coverage for quic_socket.cpp#1057
kcenon merged 1 commit into
developfrom
test/issue-1051-quic-socket-coverage

Conversation

@kcenon

@kcenon kcenon commented Apr 26, 2026

Copy link
Copy Markdown
Owner

What

Summary

Adds tests/unit/quic_socket_branch_test.cpp with 53 hermetic test cases exercising public-API surfaces of src/internal/quic_socket.cpp. Targets the #953 acceptance bar of line >= 70% / branch >= 60% by covering every reachable surface that does not require a live UDP/QUIC peer.

Change Type

  • Test

Why

Related Issues

Motivation

src/internal/quic_socket.cpp sits among the lowest-coverage files in the network_system source tree per the 2026-04-26 measurement on develop @ 05c1b7bb (line 43.7%, branch 21.3%). The uncovered region concentrates in error and boundary paths -- exactly the surfaces that regress during the ongoing Result<T> migration and API stabilization. Bringing this file over the 70% line / 60% branch bar contributes directly to the #953 acceptance criteria.

Where

Files Changed

  • tests/unit/quic_socket_branch_test.cpp (new, 718 lines, 53 tests)
  • tests/CMakeLists.txt (registration)
  • CHANGELOG.md, docs/CHANGELOG.md (Unreleased / Added entries)

How

Implementation Highlights

The test file complements quic_socket_construction_test.cpp (Issue #989) and groups tests under a single QuicSocketBranchTest fixture that owns an asio::io_context. Coverage groups:

  • Short-lived construction loops for both quic_role::client and quic_role::server (16 iterations each)
  • Default invariants for state() / role() / is_connected() / is_handshake_complete() / remote_endpoint() / remote_connection_id() (the latter two exercise the seldom-exercised query path before any connect/accept)
  • local_connection_id() RFC 9000 length bounds (0..20) and pairwise uniqueness across 16 sockets
  • Callback registration for all four callback types (stream_data, connected, error, close) with default-constructed (empty) std::function, multi-replacement (3-deep), and shared_ptr-captured state lambdas
  • Mutable + const socket() accessor reachability
  • Role-guard rejection branches: server connect() rejected with and without SNI, client accept() rejected with empty and missing PEM paths, 3-iteration repeated server-connect rejection idempotency
  • close() idempotency: idle close, application error code (is_application_error=true branch), std::numeric_limits<uint64_t>::max() error code, 1024-character reason, empty reason, triple-close
  • send_stream_data() not-connected guard for empty / populated / 64 KiB payloads with and without FIN
  • create_stream() not-connected guard for client-bidi / client-uni / server-bidi / server-uni
  • close_stream() not_found early-return for nine separate unknown stream ids including numeric_limits<uint64_t>::max()
  • stop_receive() before any start, repeated idempotency, and start-then-stop sequence
  • Move construction preserving role + connection ID + idle state with populated callbacks; server-role move construction; move assignment overwriting client target with server source; 3-step move-assignment chain; self-move-assignment via reference (covers this != &other guard)
  • Concurrent state-query polling under shared_ptr lifetime (4 threads x 200 iterations) for state / is_connected / is_handshake_complete and for role / local_connection_id and for remote_connection_id
  • Concurrent callback replacement with is_connected reader thread
  • Multi-instance independent state across 4 + 4 client/server pairs and the close-one-affects-none property
  • Destructor cleanup paths after close(), without close(), and after start_receive() (timer cancellation, async-receive cancellation)

Honest scope statement

The impl-level methods that physically exchange QUIC packets with a peer remain reachable only with a UDP transport fixture and a TLS-1.3 peer. Specifically the private surfaces:

  • do_receive() (async UDP receive completion)
  • handle_packet() (header parsing, header unprotection, payload decryption, frame parsing dispatch)
  • process_frame() and its std::visit dispatch branches for every variant in the protocols::quic::frame variant
  • process_crypto_frame() (TLS handshake state machine including client/server transition to connected)
  • process_stream_frame() (stream-data callback delivery)
  • process_ack_frame() (placeholder)
  • process_connection_close_frame() (peer-driven draining)
  • process_handshake_done_frame() (client-side handshake completion)
  • send_pending_packets() populated-queue success path with non-empty crypto / stream queues
  • send_packet() encryption + async_send_to dispatch including initial / handshake / short-header builder branches and the keys_result.is_err() failure-branch complement
  • queue_crypto_data() populated-state append branch's complement
  • determine_encryption_level() per-packet-type dispatch (initial / zero_rtt / handshake / default / short header)
  • on_retransmit_timeout() handler

all require a live UDP peer that speaks the QUIC wire format end-to-end. Driving them would require either a mock UDP peer that speaks the QUIC wire format end-to-end or a friend-declared injection point inside quic_socket. The connect() and accept() success paths past TLS init also require a peer that completes the TLS handshake, or real PEM cert / key files on disk for accept().

Testing Done

  • Tests added: 53 test cases in 1 test suite (QuicSocketBranchTest)
  • Local build: skipped -- cmake/ctest not installed in sandbox
  • Will rely on CI (Coverage Analysis + ubuntu/macos x Debug/Release matrix + ASAN/TSAN/UBSAN)

Breaking Changes

None -- test-only additions.

Adds tests/unit/quic_socket_branch_test.cpp with 53 hermetic test cases
exercising public-API surfaces of src/internal/quic_socket.cpp that
remained uncovered after Issue #989. Targets the #953 acceptance bar of
line >= 70% / branch >= 60%.

Coverage includes: short-lived construction loops for both client and
server roles; default invariants for state, role, is_connected,
is_handshake_complete, remote_endpoint, remote_connection_id;
local_connection_id RFC 9000 length bounds and pairwise uniqueness
across 16 sockets; callback registration with default-constructed,
replaced, and shared_ptr-captured handlers for all four callback types;
mutable and const socket() accessor reachability; role-guard rejection
in connect/accept (server-cannot-connect with and without SNI,
client-cannot-accept with empty and missing PEM paths, repeated
rejection idempotency); close() idempotency on idle/draining/closed
including triple-close, application-error-code branch with max-uint64
and 1024-character reason; send_stream_data not-connected guard;
create_stream not-connected guard for client/server bidi/uni;
close_stream not_found early-return for nine unknown ids;
start_receive/stop_receive flag transitions; move construction and
move assignment with populated callbacks; self-move-assignment via
reference; concurrent state-query polling and concurrent callback
replacement under shared_ptr lifetime; multi-instance independent
state; destructor cleanup paths.

Honest scope: the impl-level methods that physically exchange QUIC
packets with a peer (do_receive, handle_packet, process_frame and
its dispatch branches, process_crypto_frame, process_stream_frame,
process_ack_frame, process_connection_close_frame,
process_handshake_done_frame, send_pending_packets populated path,
send_packet encryption + async_send_to dispatch, queue_crypto_data
populated state, determine_encryption_level per-packet-type dispatch,
on_retransmit_timeout) require a live UDP/QUIC peer and remain
uncovered without a transport fixture. The connect()/accept() success
paths past TLS init also need a peer or PEM files on disk.

Closes #1051
Part of #953
@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report

Metric Value
Line Coverage 66.6%
Branch Coverage 33.0%
Target 80% lines / 70% branches
Coverage Details

Full HTML report is available as a build artifact.

@kcenon kcenon merged commit 4839892 into develop Apr 26, 2026
9 checks passed
@kcenon kcenon deleted the test/issue-1051-quic-socket-coverage branch April 26, 2026 09:12
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