Skip to content

test(grpc): expand branch coverage for client.cpp#1055

Merged
kcenon merged 1 commit into
developfrom
test/issue-1049-grpc-client-coverage
Apr 26, 2026
Merged

test(grpc): expand branch coverage for client.cpp#1055
kcenon merged 1 commit into
developfrom
test/issue-1049-grpc-client-coverage

Conversation

@kcenon

@kcenon kcenon commented Apr 26, 2026

Copy link
Copy Markdown
Owner

What

Summary

Adds hermetic branch coverage tests for src/protocols/grpc/client.cpp, exercising public-API surfaces that remained uncovered after the initial coverage work in #994. The new test file tests/unit/grpc_client_branch_test.cpp adds 51 test cases across 12 test suites, complementing the existing grpc_client_test.cpp and core_client_test.cpp.

Change Type

  • Test

Why

Related Issues

Motivation

src/protocols/grpc/client.cpp was measured at line 22.6% / branch 9.5% on develop @ 05c1b7bb (2026-04-26 baseline) and concentrates uncovered code in error and boundary paths — exactly the surfaces that regress during the ongoing Result<T> migration. Raising coverage of this file contributes directly to the #953 acceptance criteria (line >= 70% / branch >= 60% per file). This PR adds hermetic tests that exercise public-facing surfaces (config round-trip, call_options, lifecycle, guard clauses, async callbacks, metadata semantics) without requiring a live gRPC peer.

Where

Files Changed

  • tests/unit/grpc_client_branch_test.cpp (new, 542 lines, 51 test cases)
  • tests/CMakeLists.txt (registration of network_grpc_client_branch_test)
  • CHANGELOG.md (one-line entry under [Unreleased] > Added)
  • docs/CHANGELOG.md (multi-line detailed entry under [Unreleased] > Added)

How

Implementation Highlights

Test groups in grpc_client_branch_test.cpp:

  • GrpcClientChannelConfigBoundary — full-field round-trip with all-zero / all-max / default / partial mutual-TLS configurations
  • GrpcClientCallOptionsSetTimeoutset_timeout across chrono::seconds / milliseconds / microseconds / nanoseconds, zero-timeout, deadline overwrite, monotonicity
  • GrpcClientCallOptionsMetadata — empty key / empty value / 64 ordered entries / 8 KiB value / binary-flagged key / clear-resets-empty
  • GrpcClientConstructionTargets — long, IPv6 bracket, DNS, with-path, port-65535, identical-target permutations
  • GrpcClientWaitForConnectedBudget — 0 / 1 / 50 ms timeouts, repeated-call idempotency
  • GrpcClientDisconnectIdempotency — 32 sequential disconnects, 4-thread concurrent disconnects, 8-thread is_connected polling (1600 observations), concurrent disconnect-while-querying
  • GrpcClientMoveSemantics — move construct / move assign / 4-step move chain / disconnect-after-move
  • GrpcClientConnectMalformed — leading / trailing / only colon, multi-colon, space-in-port, negative-leading port, > 63-char DNS label (default-transport build only)
  • GrpcClientCallRawGuards — empty method, no-leading-slash, 64 KiB payload, pre-expired deadline, future deadline, wait_for_ready=true, compression algorithm set, post-disconnect
  • GrpcClientStreamingGuards — server / client / bidi streaming with empty methods, missing-slash methods, expired deadline, post-disconnect
  • GrpcClientCallRawAsyncStress — 8-callback concurrent stress with deterministic notification, null-callback safety with non-empty payload and with deadline
  • GrpcMetadataSemantics — copy / move / assignment-from-empty / erase

Honest scope statement

This PR covers the surfaces that are reachable via the public API without a live gRPC peer. The impl-level methods that physically exchange frames with a peer remain at zero hits and would require a transport fixture (or in-process loopback test peer) to drive:

  • grpc_client::impl::call_raw body after the is_connected()==true branch (HTTP/2 post() round-trip in default transport, stub_->UnaryCall in official-grpcpp build)
  • server_stream_reader_impl::on_data / on_headers / on_complete — invoked by HTTP/2 stream callbacks
  • client_stream_writer_impl::write / writes_done / finish round-trip — requires server response
  • bidi_stream_impl::read / write / writes_done / finish — requires bidi peer
  • grpc_client::impl::server_stream_raw / client_stream_raw / bidi_stream_raw body after the is_connected()==true and method-validation guards
  • Tracing span->set_attribute / span->set_error calls on successful call paths (only reachable when tracing is enabled and a call completes)

The hermetic suite covers everything that returns synchronously without crossing the network boundary: config validation, option construction, lifecycle, guard clauses, async-callback delivery for the not-connected case, and value-type semantics. A separate sub-issue (or in-process gRPC test fixture work under #953) is required to push coverage of the frame-exchange paths above the line/branch thresholds.

Testing Done

  • Tests added: 51 test cases in 12 test suites (~542 lines)
  • Local build: skipped — cmake/ctest not installed in sandbox
  • Will rely on CI (Coverage Analysis + ubuntu/macos x Debug/Release matrix)

Breaking Changes

None — test-only additions and CHANGELOG entries.

Add tests/unit/grpc_client_branch_test.cpp complementing grpc_client_test.cpp
(Issue #994) and core_client_test.cpp (Issue #977) with hermetic public-API
coverage targeting src/protocols/grpc/client.cpp.

Surfaces covered:
- grpc_channel_config full-field round-trip with zero/max boundary values,
  default verification, partial mutual-TLS configurations
- call_options::set_timeout across seconds/milliseconds/microseconds/
  nanoseconds, zero-timeout, deadline-overwrite, monotonicity
- call_options metadata growth: empty key, empty value, 64 ordered entries,
  8 KiB value, binary-flagged key, clear-resets-empty
- grpc_client construction with long, IPv6 bracket, DNS, with-path, port-65535,
  and identical-target permutations
- wait_for_connected budget verification at 0/1/50 ms, repeated-call idempotency
- 32-iteration sequential and 4-thread concurrent disconnect(),
  8-thread concurrent is_connected() polling, concurrent
  disconnect-while-querying
- Move construction / move assignment / 4-step move chain
- Extended malformed-target connect coverage: leading/trailing/only colon,
  multi-colon, space-in-port, negative-leading, > 63-char DNS label
- call_raw guard variants: empty method, no-leading-slash, 64 KiB payload,
  pre-expired deadline, future deadline, wait_for_ready, compression,
  post-disconnect
- Server / client / bidi streaming guard clauses: empty / no-slash methods,
  expired deadline, post-disconnect
- call_raw_async 8-callback concurrent stress, null-callback safety
- grpc_metadata copy / move / assignment-from-empty / erase semantics

Honest scope statement: impl-level frame exchange paths (HTTP/2 POST after
is_connected()==true, official UnaryCall, streaming send/receive loops,
on_data/on_headers/on_complete callbacks driven by HTTP/2 trailers, tracing
set_attribute/set_error inside successful call paths) require a live peer
and remain uncovered by this hermetic suite.

Closes #1049
@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report

Metric Value
Line Coverage 66.1%
Branch Coverage 32.7%
Target 80% lines / 70% branches
Coverage Details

Full HTML report is available as a build artifact.

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