88#include " mock_sse_server.hpp"
99
1010#include < boost/asio/io_context.hpp>
11- #include < boost/asio/steady_timer.hpp>
1211
1312#include < atomic>
1413#include < chrono>
@@ -23,12 +22,13 @@ using namespace std::chrono_literals;
2322namespace {
2423
2524// C++17-compatible latch replacement
25+ // https://en.cppreference.com/w/cpp/thread/latch.html
2626class SimpleLatch {
2727public:
28- explicit SimpleLatch (std::size_t count) : count_(count) {}
28+ explicit SimpleLatch (const std::size_t count) : count_(count) {}
2929
3030 void count_down () {
31- std::lock_guard<std::mutex> lock (mutex_);
31+ std::lock_guard lock (mutex_);
3232 if (count_ > 0 ) {
3333 --count_;
3434 }
@@ -37,7 +37,7 @@ class SimpleLatch {
3737
3838 template <typename Rep, typename Period>
3939 bool wait_for (std::chrono::duration<Rep, Period> timeout) {
40- std::unique_lock<std::mutex> lock (mutex_);
40+ std::unique_lock lock (mutex_);
4141 return cv_.wait_for (lock, timeout, [this ] { return count_ == 0 ; });
4242 }
4343
@@ -82,12 +82,6 @@ class EventCollector {
8282 return errors_;
8383 }
8484
85- void clear () {
86- std::lock_guard<std::mutex> lock (mutex_);
87- events_.clear ();
88- errors_.clear ();
89- }
90-
9185private:
9286 mutable std::mutex mutex_;
9387 std::condition_variable cv_;
@@ -456,7 +450,7 @@ TEST(CurlClientTest, Handles500Error) {
456450 std::atomic<int > connection_attempts{0 };
457451
458452 auto handler = [&](auto const &, auto send_response, auto , auto ) {
459- connection_attempts++ ;
453+ ++connection_attempts ;
460454 http::response<http::string_body> res{http::status::internal_server_error, 11 };
461455 res.body () = " Error" ;
462456 res.prepare_payload ();
@@ -585,7 +579,7 @@ TEST(CurlClientTest, HandlesImmediateClose) {
585579 std::atomic<int > connection_attempts{0 };
586580
587581 auto handler = [&](auto const &, auto , auto , auto close) {
588- connection_attempts++ ;
582+ ++connection_attempts ;
589583 close (); // Immediately close without sending headers
590584 };
591585
@@ -664,59 +658,26 @@ TEST(CurlClientTest, RespectsReadTimeout) {
664658 EXPECT_TRUE (shutdown_latch.wait_for (100ms));
665659}
666660
667- // Resource management tests
668-
669- TEST (CurlClientTest, NoThreadLeaksAfterMultipleConnections) {
670- // This test verifies that threads are properly joined and not leaked
671- MockSSEServer server;
672- auto port = server.start (TestHandlers::simple_event (" test" ));
673-
674- IoContextRunner runner;
675-
676- // Create and destroy multiple clients
677- for (int i = 0 ; i < 5 ; i++) {
678- EventCollector collector;
679-
680- auto client = Builder (runner.context ().get_executor (), " http://localhost:" + std::to_string (port))
681- .receiver ([&](Event e) { collector.add_event (std::move (e)); })
682- .build ();
683-
684- client->async_connect ();
685- ASSERT_TRUE (collector.wait_for_events (1 ));
686-
687- SimpleLatch shutdown_latch (1 );
688- client->async_shutdown ([&] { shutdown_latch.count_down (); });
689- EXPECT_TRUE (shutdown_latch.wait_for (5000ms));
690-
691- // Client should be cleanly destroyed here
692- }
693-
694- // If threads weren't properly joined, we'd likely see issues here
695- // The test passing indicates proper resource cleanup
696- }
697-
698661TEST (CurlClientTest, DestructorCleansUpProperly) {
699- MockSSEServer server;
700- auto port = server.start ([](auto const &, auto send_response, auto send_sse_event, auto ) {
701- http::response<http::string_body> res{http::status::ok, 11 };
702- res.set (http::field::content_type, " text/event-stream" );
703- res.chunked (true );
704- send_response (res);
705-
706- // Keep sending events
707- for (int i = 0 ; i < 100 ; i++) {
708- send_sse_event (SSEFormatter::event (" event " + std::to_string (i)));
709- std::this_thread::sleep_for (10ms);
710- }
711- });
712-
713- IoContextRunner runner;
714- EventCollector collector;
715-
716662 {
663+ MockSSEServer server;
664+ auto port = server.start ([](auto const &, auto send_response, auto send_sse_event, auto ) {
665+ http::response<http::string_body> res{http::status::ok, 11 };
666+ res.set (http::field::content_type, " text/event-stream" );
667+ res.chunked (true );
668+ send_response (res);
669+
670+ // Keep sending events
671+ for (int i = 0 ; i < 100 ; i++) {
672+ send_sse_event (SSEFormatter::event (" event " + std::to_string (i)));
673+ std::this_thread::sleep_for (10ms);
674+ }
675+ });
676+ EventCollector collector;
677+ IoContextRunner runner;
717678 auto client = Builder (runner.context ().get_executor (), " http://localhost:" + std::to_string (port))
718- .receiver ([&](Event e) { collector.add_event (std::move (e)); })
719- .build ();
679+ .receiver ([&](Event e) { collector.add_event (std::move (e)); })
680+ .build ();
720681
721682 client->async_connect ();
722683 ASSERT_TRUE (collector.wait_for_events (1 ));
@@ -728,8 +689,6 @@ TEST(CurlClientTest, DestructorCleansUpProperly) {
728689 // Test passing indicates proper cleanup in destructor
729690}
730691
731- // Edge case tests
732-
733692TEST (CurlClientTest, HandlesEmptyEventData) {
734693 MockSSEServer server;
735694 auto port = server.start ([](auto const &, auto send_response, auto send_sse_event, auto close) {
@@ -798,9 +757,9 @@ TEST(CurlClientTest, HandlesEventWithOnlyType) {
798757
799758TEST (CurlClientTest, HandlesRapidEvents) {
800759 MockSSEServer server;
801- const int num_events = 100 ;
760+ constexpr int num_events = 100 ;
802761
803- auto port = server.start ([num_events ](auto const &, auto send_response, auto send_sse_event, auto close) {
762+ auto port = server.start ([](auto const &, auto send_response, auto send_sse_event, auto close) {
804763 http::response<http::string_body> res{http::status::ok, 11 };
805764 res.set (http::field::content_type, " text/event-stream" );
806765 res.chunked (true );
@@ -832,14 +791,12 @@ TEST(CurlClientTest, HandlesRapidEvents) {
832791 EXPECT_TRUE (shutdown_latch.wait_for (5000ms));
833792}
834793
835- // Shutdown-specific tests - critical for preventing crashes/hangs in user applications
836-
837794TEST (CurlClientTest, ShutdownDuringBackoffDelay) {
838795 // This ensures clean shutdown during backoff/retry wait period
839796 std::atomic<int > connection_attempts{0 };
840797
841798 auto handler = [&](auto const &, auto send_response, auto , auto ) {
842- connection_attempts++ ;
799+ ++connection_attempts ;
843800 // Return 500 to trigger backoff
844801 http::response<http::string_body> res{http::status::internal_server_error, 11 };
845802 res.body () = " Error" ;
@@ -906,7 +863,7 @@ TEST(CurlClientTest, ShutdownDuringDataReception) {
906863
907864 IoContextRunner runner;
908865 // Shared ptr to prevent handling events during destruction.
909- std::shared_ptr<EventCollector> collector = std::make_shared<EventCollector>();
866+ auto collector = std::make_shared<EventCollector>();
910867
911868 auto client = Builder (runner.context ().get_executor (), " http://localhost:" + std::to_string (port))
912869 .receiver ([collector, &client_received_some](Event e) {
0 commit comments