test(websocket): expand websocket_server.cpp coverage to 80% line / 70% branch#1081
Merged
Merged
Conversation
5 tasks
Contributor
Coverage Report
Coverage DetailsFull HTML report is available as a build artifact. |
Adds tests/unit/websocket_server_loopback_test.cpp registered via
add_network_test. The new fixture drives messaging_ws_server end-to-end
against a real RFC 6455 client (built from internal::websocket_socket
+ tcp_socket on a probed free port) so that branches reachable only
via a live handshake are exercised:
- do_start_impl / do_stop_impl success paths
- start_server(uint16_t,sv) already-running early-return
- start_server(ws_server_config&) overload
- i_websocket_server::start(port) interface delegation past start
- do_accept loop with multiple concurrent clients
- handle_new_connection success / max_connections-zero limit /
handshake-failure (raw garbage payload) branches
- on_message text + binary dispatch in invoke_message_callback
- on_close path with peer-initiated close frame, including
connection_count return-to-zero
- broadcast_text / broadcast_binary populated-session branches
- get_connection / get_all_connections success branches
- ws_connection accessors (id, is_connected, path, remote_endpoint)
and send/send_text/send_binary/close (no-arg + code+reason) reachable
only after a real handshake
- auto_pong true and false branches in handle_new_connection's
ping_callback
- bind_failed catch arm of do_start_impl (port held by a separate
acceptor)
- ~messaging_ws_server while running calling stop_server
The hermetic file test_messaging_ws_server.cpp explicitly documents a
no-peer / no-io_context invariant; mixing loopback tests there would
break that invariant. The new file is placed under tests/unit/ to align
with the existing websocket_server_branch_test.cpp / websocket_server_test.cpp
convention for the same class.
Local build verification was not possible (no C++ toolchain available
in the sandbox); CI is the source of truth for AC #1-#4. AC #5 (#953
post-merge coverage delta) is tracked separately.
Part of #1067
Add Unreleased entries to root CHANGELOG.md and docs/CHANGELOG.md (SSOT) covering the new tests/unit/websocket_server_loopback_test.cpp and the exact branches it exercises (do_start_impl/do_stop_impl success paths, do_accept loop, handle_new_connection success/limit/handshake-failure branches, on_message text+binary dispatch, on_close, broadcast populated- session branches, get_connection/get_all_connections success, auto_pong true/false branches, bind_failed catch arm, ~messaging_ws_server while running, ws_connection/ws_connection_impl methods). Part of #1067
64aeaf8 to
873c0ce
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Summary
Adds in-process loopback unit tests that drive
messaging_ws_server(
src/http/websocket_server.cpp) end-to-end against a real RFC 6455client built from
internal::websocket_socket+internal::tcp_socket,exercising branches that the existing hermetic suites
(
test_messaging_ws_server.cpp,tests/unit/websocket_server_branch_test.cpp,tests/unit/websocket_server_test.cpp) explicitly document as out ofreach without a live handshake.
Change Type
Affected Components
tests/unit/websocket_server_loopback_test.cpp— new, ~620 linestests/CMakeLists.txt— registernetwork_websocket_server_loopback_testCHANGELOG.md,docs/CHANGELOG.md— record the new coverage workWhy
Problem Solved
src/http/websocket_server.cppmeasured 39.9% line / 19.7% branch ondevelop @ 2026-04-26 (run 24947193873) — priority #6
worst-coverage target. The hermetic branch tests added in #1053 cover the
public-API surfaces reachable on a never-started server, but explicitly
note in the file's "Honest scope statement" that
do_start_impl/do_stop_implsuccess paths,do_accept,handle_new_connection,on_message/on_close/on_error, the fourinvoke_*_callbackhelpers, the populated-session broadcast/get_connectionbranches, and the
ws_connection/ws_connection_implmethods arereachable only with a live TCP client that completes the WebSocket
HTTP/1.1 upgrade handshake. This PR closes that gap.
Related Issues
Alternatives Considered
tests/test_messaging_ws_server.cppin place — rejectedbecause that file's header explicitly documents a no-peer / no-io_context
invariant ("All tests operate purely on the public API; no real
WebSocket client or live HTTP/1.1 upgrade handshake is required.").
Mixing loopback tests there would break that invariant and confuse
future maintainers.
messaging_ws_server— rejectedbecause it would require a production-code change for testing only.
The probe-port + real-handshake approach exercises the same branches
without altering the production class surface.
the goal is exactly to drive the real
do_acceptloop and per-framedispatch; mocking the thread pool would re-stub out the very paths
the test is trying to cover.
Where
Files Changed
tests/unit/websocket_server_loopback_test.cpptests/CMakeLists.txtnetwork_websocket_server_loopback_testviaadd_network_testCHANGELOG.md[Unreleased]### Addeddocs/CHANGELOG.md[Unreleased]### AddedTest Groups
WsServerLoopbackLifecycledo_start_implsuccess,do_stop_implsuccess,start_server(uint16_t,sv)already-running,start_server(ws_server_config&)overload,i_websocket_server::start(port)delegation, restart cycle,bind_failedcatch arm,~messaging_ws_serverwhile runningWsServerLoopbackMessagesdo_acceptsuccess,handle_new_connectionsuccess,on_messagetext + binary dispatch ininvoke_message_callback, connection_callback firing, raw message slot exerciseWsServerLoopbackCloseon_closepath, peer-initiated close frame,disconnection_callbackinvocation,connection_countreturn-to-zeroWsServerLoopbackBroadcastbroadcast_text/broadcast_binarypopulated-session_mgr branchesWsServerLoopbackLookupget_all_connectionsnon-empty,get_connectionsuccess + unknown-id-after-startWsServerLoopbackWsConnectionws_connectionaccessors (id,is_connected,path,remote_endpoint) and send/close methods (send_text,send_binary,send,close(),close(uint16_t,string_view))WsServerLoopbackAutoPonghandle_new_connectionping_callbackif(config_.auto_pong)true and false branchesWsServerLoopbackLimitshandle_new_connectioncan_accept_connection()==falsebranch (max_connections=0)WsServerLoopbackHandshakehandle_new_connectionws_socket->async_acceptfailure branch (raw garbage from peer)WsServerLoopbackPostStopbroadcast/wait_for_stopsafetyWsServerLoopbackConcurrentdo_acceptloop iterates more than onceHow
Implementation Highlights
probe_free_port()— opens an asio acceptor on port 0 to discover anOS-assigned port, then closes it before the server binds. There is a
brief race window in which the port could be reused — accepted as a
known limitation for tests in this file (consistent with the existing
tests/integration/test_websocket_e2e.cpppattern).wait_until(predicate, timeout)— 5 ms-poll predicate with 2 s defaultbudget; used to synchronize on async server-side state transitions
(callback invocations, session_mgr updates) without resorting to
fixed
sleep_forcalls.WsLoopbackClient— minimal RFC 6455 client wrappinginternal::tcp_socketand
internal::websocket_socketwith its ownasio::io_contextandthread; supports
connect/send_text/send_binary/send_ping/
closeand exposesset_message_handler/set_pong_handlerforobserving server-to-client frames (used by the broadcast tests).
asio::ip::tcp::socket(nowebsocket_socketwrapper) and writes non-HTTP bytes. The server'sws_socket->async_acceptparses this, fails validation, and theconnection is dropped without registration — exercising exactly the
if (ec)failure arm inhandle_new_connection.bind_failedtest holds the port with our ownasio::ip::tcp::acceptorbefore the server tries to bind.
do_start_impl'stryblock catchesthe
std::exceptionand returnsbind_failedwhilelifecycle_.mark_stopped()rolls back the running flag.Testing Done
available in the sandbox:
cmake,g++,clang++,ninjaallreport not found). CI is the source of truth for AC feat: Phase 1 - Network System Separation from messaging_system #1-feat: Phase 4 - messaging_system Update and Performance Optimization #4.
src/http/websocket_server.cpp(every test references a specificbranch with file:line traceability)
tests/integration/test_websocket_e2e.cppforthe loopback pattern (same
internal::websocket_socketclientconstruction, same handshake-via-promise idiom)
add_network_testmacro confirmedconsistent with sibling
network_websocket_server_branch_testTest Plan for Reviewers
coverage.ymlworkflow(post-merge metrics will be tracked under Expand unit test coverage from 40% to 80% #953 per AC feat: Phase 5 - Production Readiness and Release Preparation #5).
Breaking Changes
None. This PR adds tests only.
Rollback Plan
Revert this PR. No production code is modified.
Acceptance Criteria
src/http/websocket_server.cppline coverage >= 80% — CI must confirmsrc/http/websocket_server.cppbranch coverage >= 70% — CI must confirmChecklist
tests/unit/websocket_server_branch_test.cppformatting)Closes #1067keyword