From bb6f6ab8062f767f45a59db80eb10578fa8e4403 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Wed, 20 Mar 2019 16:28:39 -0700 Subject: [PATCH 1/9] stash --- .../cpp/include/signalrclient/connection.h | 7 +- .../include/signalrclient/hub_connection.h | 9 +- .../cpp/src/signalrclient/connection.cpp | 12 +- .../cpp/src/signalrclient/connection_impl.cpp | 55 +- .../cpp/src/signalrclient/connection_impl.h | 7 +- .../cpp/src/signalrclient/hub_connection.cpp | 16 +- .../src/signalrclient/hub_connection_impl.cpp | 81 +- .../src/signalrclient/hub_connection_impl.h | 8 +- .../connection_impl_tests.cpp | 627 +++++-- .../hub_connection_impl_tests.cpp | 1488 ++++++++--------- .../cpp/test/signalrclienttests/test_utils.h | 17 +- 11 files changed, 1314 insertions(+), 1013 deletions(-) diff --git a/src/SignalR/clients/cpp/include/signalrclient/connection.h b/src/SignalR/clients/cpp/include/signalrclient/connection.h index c5f48cd3c3ff..fe646a6f5f26 100644 --- a/src/SignalR/clients/cpp/include/signalrclient/connection.h +++ b/src/SignalR/clients/cpp/include/signalrclient/connection.h @@ -6,7 +6,6 @@ #include "_exports.h" #include #include -#include "pplx/pplxtasks.h" #include "connection_state.h" #include "trace_level.h" #include "log_writer.h" @@ -29,16 +28,16 @@ namespace signalr connection& operator=(const connection&) = delete; - SIGNALRCLIENT_API pplx::task __cdecl start(); + SIGNALRCLIENT_API void __cdecl start(std::function callback); - SIGNALRCLIENT_API pplx::task __cdecl send(const std::string& data); + SIGNALRCLIENT_API void __cdecl send(const std::string& data, std::function callback); SIGNALRCLIENT_API void __cdecl set_message_received(const message_received_handler& message_received_callback); SIGNALRCLIENT_API void __cdecl set_disconnected(const std::function& disconnected_callback); SIGNALRCLIENT_API void __cdecl set_client_config(const signalr_client_config& config); - SIGNALRCLIENT_API pplx::task __cdecl stop(); + SIGNALRCLIENT_API void __cdecl stop(std::function callback); SIGNALRCLIENT_API connection_state __cdecl get_connection_state() const noexcept; SIGNALRCLIENT_API std::string __cdecl get_connection_id() const; diff --git a/src/SignalR/clients/cpp/include/signalrclient/hub_connection.h b/src/SignalR/clients/cpp/include/signalrclient/hub_connection.h index 4eea630f3398..35991acefe2f 100644 --- a/src/SignalR/clients/cpp/include/signalrclient/hub_connection.h +++ b/src/SignalR/clients/cpp/include/signalrclient/hub_connection.h @@ -6,7 +6,6 @@ #include "_exports.h" #include #include -#include "pplx/pplxtasks.h" #include "cpprest/json.h" #include "connection_state.h" #include "trace_level.h" @@ -31,8 +30,8 @@ namespace signalr hub_connection& operator=(const hub_connection&) = delete; - SIGNALRCLIENT_API pplx::task __cdecl start(); - SIGNALRCLIENT_API pplx::task __cdecl stop(); + SIGNALRCLIENT_API void __cdecl start(std::function callback); + SIGNALRCLIENT_API void __cdecl stop(std::function callback); SIGNALRCLIENT_API connection_state __cdecl get_connection_state() const; SIGNALRCLIENT_API std::string __cdecl get_connection_id() const; @@ -43,9 +42,9 @@ namespace signalr SIGNALRCLIENT_API void __cdecl on(const std::string& event_name, const method_invoked_handler& handler); - SIGNALRCLIENT_API pplx::task invoke(const std::string& method_name, const web::json::value& arguments = web::json::value::array()); + SIGNALRCLIENT_API void invoke(const std::string& method_name, const web::json::value& arguments = web::json::value::array(), std::function callback = [](const web::json::value&, std::exception_ptr) {}); - SIGNALRCLIENT_API pplx::task send(const std::string& method_name, const web::json::value& arguments = web::json::value::array()); + SIGNALRCLIENT_API void send(const std::string& method_name, const web::json::value& arguments = web::json::value::array(), std::function callback = [](std::exception_ptr) {}); private: std::shared_ptr m_pImpl; diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection.cpp b/src/SignalR/clients/cpp/src/signalrclient/connection.cpp index 0b7bfb8f3c19..4d5ad220fe64 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/connection.cpp @@ -16,14 +16,14 @@ namespace signalr // undefinded behavior since we are using an incomplete type. More details here: http://herbsutter.com/gotw/_100/ connection::~connection() = default; - pplx::task connection::start() + void connection::start(std::function callback) { - return m_pImpl->start(); + m_pImpl->start(callback); } - pplx::task connection::send(const std::string& data) + void connection::send(const std::string& data, std::function callback) { - return m_pImpl->send(data); + m_pImpl->send(data, callback); } void connection::set_message_received(const message_received_handler& message_received_callback) @@ -41,9 +41,9 @@ namespace signalr m_pImpl->set_client_config(config); } - pplx::task connection::stop() + void connection::stop(std::function callback) { - return m_pImpl->stop(); + m_pImpl->stop(callback); } connection_state connection::get_connection_state() const noexcept diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp index 3ac69d7382da..882a625aba29 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp @@ -74,14 +74,14 @@ namespace signalr change_state(connection_state::disconnected); } - pplx::task connection_impl::start() + void connection_impl::start(std::function callback) { { std::lock_guard lock(m_stop_lock); if (!change_state(connection_state::disconnected, connection_state::connecting)) { - return pplx::task_from_exception( - signalr_exception("cannot start a connection that is not in the disconnected state")); + callback(std::make_exception_ptr(signalr_exception("cannot start a connection that is not in the disconnected state"))); + return; } // there should not be any active transport at this point @@ -92,7 +92,19 @@ namespace signalr m_connection_id = ""; } - return start_negotiate(m_base_url, 0); + start_negotiate(m_base_url, 0) + .then([callback](pplx::task prev_task) + { + try + { + prev_task.get(); + callback(nullptr); + } + catch (...) + { + callback(std::current_exception()); + } + }); } pplx::task connection_impl::start_negotiate(const std::string& url, int redirect_count) @@ -362,7 +374,7 @@ namespace signalr } } - pplx::task connection_impl::send(const std::string& data) + void connection_impl::send(const std::string& data, std::function callback) { // To prevent an (unlikely) condition where the transport is nulled out after we checked the connection_state // and before sending data we store the pointer in the local variable. In this case `send()` will throw but @@ -372,17 +384,17 @@ namespace signalr const auto connection_state = get_connection_state(); if (connection_state != signalr::connection_state::connected || !transport) { - return pplx::task_from_exception(signalr_exception( + callback(std::make_exception_ptr(signalr_exception( std::string("cannot send data when the connection is not in the connected state. current connection state: ") - .append(translate_connection_state(connection_state)))); + .append(translate_connection_state(connection_state))))); + return; } auto logger = m_logger; logger.log(trace_level::info, std::string("sending data: ").append(data)); - pplx::task_completion_event event; - transport->send(data, [logger, event](std::exception_ptr exception) + transport->send(data, [logger, callback](std::exception_ptr exception) mutable { try { @@ -390,7 +402,7 @@ namespace signalr { std::rethrow_exception(exception); } - event.set(); + callback(nullptr); } catch (const std::exception &e) { @@ -399,21 +411,29 @@ namespace signalr std::string("error sending data: ") .append(e.what())); - event.set_exception(exception); + callback(exception); } }); - - return pplx::create_task(event); } - pplx::task connection_impl::stop() + void connection_impl::stop(std::function callback) { m_logger.log(trace_level::info, "stopping connection"); auto connection = shared_from_this(); - return shutdown() - .then([connection]() + shutdown() + .then([connection, callback](pplx::task prev_task) { + try + { + prev_task.get(); + } + catch (...) + { + callback(std::current_exception()); + return; + } + { // the lock prevents a race where the user calls `stop` on a disconnected connection and calls `start` // on a different thread at the same time. In this case we must not null out the transport if we are @@ -429,6 +449,7 @@ namespace signalr try { connection->m_disconnected(); + callback(nullptr); } catch (const std::exception &e) { @@ -436,12 +457,14 @@ namespace signalr trace_level::errors, std::string("disconnected callback threw an exception: ") .append(e.what())); + callback(std::make_exception_ptr(e)); } catch (...) { connection->m_logger.log( trace_level::errors, std::string("disconnected callback threw an unknown exception")); + callback(std::current_exception()); } }); } diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h index 1972c4ebc118..3862341b86fc 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h @@ -5,7 +5,6 @@ #include #include -#include "cpprest/http_client.h" #include "signalrclient/http_client.h" #include "signalrclient/trace_level.h" #include "signalrclient/connection_state.h" @@ -37,9 +36,9 @@ namespace signalr ~connection_impl(); - pplx::task start(); - pplx::task send(const std::string &data); - pplx::task stop(); + void start(std::function callback); + void send(const std::string &data, std::function callback); + void stop(std::function callback); connection_state get_connection_state() const noexcept; std::string get_connection_id() const noexcept; diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp b/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp index 3dee3cdbb006..d841f8663160 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp @@ -17,14 +17,14 @@ namespace signalr // undefinded behavior since we are using an incomplete type. More details here: http://herbsutter.com/gotw/_100/ hub_connection::~hub_connection() = default; - pplx::task hub_connection::start() + void hub_connection::start(std::function callback) { - return m_pImpl->start(); + m_pImpl->start(callback); } - pplx::task hub_connection::stop() + void hub_connection::stop(std::function callback) { - return m_pImpl->stop(); + m_pImpl->stop(callback); } void hub_connection::on(const std::string& event_name, const method_invoked_handler& handler) @@ -37,24 +37,24 @@ namespace signalr return m_pImpl->on(event_name, handler); } - pplx::task hub_connection::invoke(const std::string& method_name, const web::json::value& arguments) + void hub_connection::invoke(const std::string& method_name, const web::json::value& arguments, std::function callback) { if (!m_pImpl) { throw signalr_exception("invoke() cannot be called on uninitialized hub_connection instance"); } - return m_pImpl->invoke(method_name, arguments); + return m_pImpl->invoke(method_name, arguments, callback); } - pplx::task hub_connection::send(const std::string& method_name, const web::json::value& arguments) + void hub_connection::send(const std::string& method_name, const web::json::value& arguments, std::function callback) { if (!m_pImpl) { throw signalr_exception("send() cannot be called on uninitialized hub_connection instance"); } - return m_pImpl->send(method_name, arguments); + m_pImpl->send(method_name, arguments, callback); } connection_state hub_connection::get_connection_state() const diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp index a8d9a83f2113..4c7fc112dc71 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp @@ -96,7 +96,7 @@ namespace signalr m_subscriptions.insert(std::pair> {event_name, handler}); } - pplx::task hub_connection_impl::start() + void hub_connection_impl::start(std::function callback) { if (m_connection->get_connection_state() != connection_state::disconnected) { @@ -108,29 +108,30 @@ namespace signalr m_handshakeTask = pplx::task_completion_event(); m_handshakeReceived = false; std::weak_ptr weak_connection = shared_from_this(); - return m_connection->start() - .then([weak_connection](pplx::task startTask) + return m_connection->start([weak_connection, callback](std::exception_ptr exception) { - startTask.get(); + //startTask.get(); auto connection = weak_connection.lock(); if (!connection) { // The connection has been destructed - return pplx::task_from_exception(signalr_exception("the hub connection has been deconstructed")); + callback(std::make_exception_ptr(signalr_exception("the hub connection has been deconstructed"))); + return; } - return connection->m_connection->send("{\"protocol\":\"json\",\"version\":1}\x1e") - .then([weak_connection](pplx::task previous_task) + + connection->m_connection->send("{\"protocol\":\"json\",\"version\":1}\x1e", [weak_connection, callback](std::exception_ptr exception) + { + auto connection = weak_connection.lock(); + if (!connection) { - auto connection = weak_connection.lock(); - if (!connection) - { - // The connection has been destructed - return pplx::task_from_exception(signalr_exception("the hub connection has been deconstructed")); - } - previous_task.get(); - return pplx::task(connection->m_handshakeTask); - }) - .then([weak_connection](pplx::task previous_task) + // The connection has been destructed + callback(std::make_exception_ptr(signalr_exception("the hub connection has been deconstructed"))); + return; + } + //previous_task.get(); + //return pplx::task(connection->m_handshakeTask); + }); + /* .then([weak_connection](pplx::task previous_task) { try { @@ -149,14 +150,14 @@ namespace signalr } throw e; } - }); + });*/ }); } - pplx::task hub_connection_impl::stop() + void hub_connection_impl::stop(std::function callback) { m_callback_manager.clear(json::value::parse(_XPLATSTR("{ \"error\" : \"connection was stopped before invocation result was received\"}"))); - return m_connection->stop(); + m_connection->stop(callback); } enum MessageType @@ -275,7 +276,7 @@ namespace signalr return true; } - pplx::task hub_connection_impl::invoke(const std::string& method_name, const json::value& arguments) + void hub_connection_impl::invoke(const std::string& method_name, const json::value& arguments, std::function callback) { _ASSERTE(arguments.is_array()); @@ -288,10 +289,10 @@ namespace signalr invoke_hub_method(method_name, arguments, callback_id, nullptr, [tce](const std::exception_ptr e){ tce.set_exception(e); }); - return pplx::create_task(tce); + //return pplx::create_task(tce); } - pplx::task hub_connection_impl::send(const std::string& method_name, const json::value& arguments) + void hub_connection_impl::send(const std::string& method_name, const json::value& arguments, std::function callback) { _ASSERTE(arguments.is_array()); @@ -301,7 +302,7 @@ namespace signalr [tce]() { tce.set(); }, [tce](const std::exception_ptr e){ tce.set_exception(e); }); - return pplx::create_task(tce); + //return pplx::create_task(tce); } void hub_connection_impl::invoke_hub_method(const std::string& method_name, const json::value& arguments, @@ -319,28 +320,26 @@ namespace signalr // weak_ptr prevents a circular dependency leading to memory leak and other problems auto weak_hub_connection = std::weak_ptr(shared_from_this()); - m_connection->send(utility::conversions::to_utf8string(request.serialize() + _XPLATSTR('\x1e'))) - .then([set_completion, set_exception, weak_hub_connection, callback_id](pplx::task send_task) + m_connection->send(utility::conversions::to_utf8string(request.serialize() + _XPLATSTR('\x1e')), [set_completion, set_exception, weak_hub_connection, callback_id](std::exception_ptr exception) + { + if (exception) { - try + set_exception(exception); + auto hub_connection = weak_hub_connection.lock(); + if (hub_connection) { - send_task.get(); - if (callback_id.empty()) - { - // complete nonBlocking call - set_completion(); - } + hub_connection->m_callback_manager.remove_callback(callback_id); } - catch (const std::exception&) + } + else + { + if (callback_id.empty()) { - set_exception(std::current_exception()); - auto hub_connection = weak_hub_connection.lock(); - if (hub_connection) - { - hub_connection->m_callback_manager.remove_callback(callback_id); - } + // complete nonBlocking call + set_completion(); } - }); + } + }); } connection_state hub_connection_impl::get_connection_state() const noexcept diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h index ca3b6c586a8d..a265d048a6dd 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h @@ -32,11 +32,11 @@ namespace signalr void on(const std::string& event_name, const std::function& handler); - pplx::task invoke(const std::string& method_name, const json::value& arguments); - pplx::task send(const std::string& method_name, const json::value& arguments); + void invoke(const std::string& method_name, const json::value& arguments, std::function callback); + void send(const std::string& method_name, const json::value& arguments, std::function callback); - pplx::task start(); - pplx::task stop(); + void start(std::function callback); + void stop(std::function callback); connection_state get_connection_state() const noexcept; std::string get_connection_id() const; diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp index 9da56d17d477..20d575f22425 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp @@ -38,11 +38,21 @@ TEST(connection_impl_start, cannot_start_non_disconnected_exception) /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client); - connection->start().wait(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); try { - connection->start().get(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + mre.get(); ASSERT_TRUE(false); // exception not thrown } catch (const signalr_exception& e) @@ -62,21 +72,14 @@ TEST(connection_impl_start, connection_state_is_connecting_when_connection_is_be auto connection = create_connection(websocket_client, writer, trace_level::errors); - auto startTask = connection->start() - // this test is not set up to connect successfully so we have to observe exceptions otherwise - // other tests may fail due to an unobserved exception from the outstanding start task - .then([](pplx::task start_task) - { - try - { - start_task.get(); - } - catch (...) - { } - }); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); ASSERT_EQ(connection->get_connection_state(), connection_state::connecting); - startTask.get(); + mre.get(); } TEST(connection_impl_start, connection_state_is_connected_when_connection_established_succesfully) @@ -84,7 +87,12 @@ TEST(connection_impl_start, connection_state_is_connected_when_connection_establ auto websocket_client = create_test_websocket_client( /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client); - connection->start().get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + mre.get(); ASSERT_EQ(connection->get_connection_state(), connection_state::connected); } @@ -99,12 +107,13 @@ TEST(connection_impl_start, connection_state_is_disconnected_when_connection_can connection_impl::create(create_uri(), trace_level::none, std::make_shared(), std::move(http_client), std::make_unique()); - try + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) { - connection->start().get(); - } - catch (...) - { } + mre.set(exception); + }); + + mre.get(); ASSERT_EQ(connection->get_connection_state(), connection_state::disconnected); } @@ -118,9 +127,15 @@ TEST(connection_impl_start, throws_for_invalid_uri) auto connection = connection_impl::create(":1\t รค bad_uri&a=b", trace_level::errors, writer, create_test_http_client(), std::make_unique(websocket_client)); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + try { - connection->start().get(); + mre.get(); ASSERT_TRUE(false); } catch (const std::exception&) @@ -147,13 +162,13 @@ TEST(connection_impl_start, start_sets_id_query_string) auto connection = connection_impl::create(create_uri(""), trace_level::errors, writer, create_test_http_client(), std::make_unique(websocket_client)); - try - { - connection->start().get(); - } - catch (...) + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) { - } + mre.set(exception); + }); + + mre.get(); ASSERT_EQ("id=f7707523-307d-4cba-9abf-3eef701241e8", query_string); } @@ -174,13 +189,13 @@ TEST(connection_impl_start, start_appends_id_query_string) auto connection = connection_impl::create(create_uri("a=b&c=d"), trace_level::errors, writer, create_test_http_client(), std::make_unique(websocket_client)); - try + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) { - connection->start().get(); - } - catch (...) - { - } + mre.set(exception); + }); + + mre.get(); ASSERT_EQ("a=b&c=d&id=f7707523-307d-4cba-9abf-3eef701241e8", query_string); } @@ -198,12 +213,13 @@ TEST(connection_impl_start, start_logs_exceptions) connection_impl::create(create_uri(), trace_level::errors, writer, std::move(http_client), std::make_unique()); - try + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) { - connection->start().get(); - } - catch (...) - { } + mre.set(exception); + }); + + mre.get(); auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); ASSERT_FALSE(log_entries.empty()); @@ -224,9 +240,15 @@ TEST(connection_impl_start, start_propagates_exceptions_from_negotiate) connection_impl::create(create_uri(), trace_level::none, std::make_shared(), std::move(http_client), std::make_unique()); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + try { - connection->start().get(); + mre.get(); ASSERT_TRUE(false); // exception not thrown } catch (const std::exception &e) @@ -249,9 +271,15 @@ TEST(connection_impl_start, start_fails_if_transport_connect_throws) auto connection = create_connection(websocket_client, writer, trace_level::errors); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + try { - connection->start().get(); + mre.get(); ASSERT_TRUE(false); // exception not thrown } catch (const std::exception &e) @@ -280,11 +308,22 @@ TEST(connection_impl_send, send_fails_if_transport_fails_when_receiving_messages auto connection = create_connection(websocket_client, writer, trace_level::errors); - connection->start().get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); + + connection->send("message", [&mre](std::exception_ptr exception) + { + mre.set_exception(exception); + }); try { - connection->send("message").get(); + mre.get(); ASSERT_TRUE(false); // exception not thrown } catch (const std::exception &e) @@ -320,9 +359,15 @@ TEST(connection_impl_start, start_fails_if_negotiate_request_fails) connection_impl::create(create_uri(), trace_level::messages, writer, std::move(http_client), std::make_unique(websocket_client)); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set_exception(exception); + }); + try { - connection->start().get(); + mre.get(); ASSERT_TRUE(false); // exception not thrown } catch (const std::exception &e) @@ -361,9 +406,15 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_has_error) connection_impl::create(create_uri(), trace_level::messages, writer, std::move(http_client), std::make_unique(websocket_client)); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set_exception(exception); + }); + try { - connection->start().get(); + mre.get(); ASSERT_TRUE(false); // exception not thrown } catch (const signalr_exception & e) @@ -404,9 +455,15 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_webs connection_impl::create(create_uri(), trace_level::messages, writer, std::move(http_client), std::make_unique(websocket_client)); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set_exception(exception); + }); + try { - connection->start().get(); + mre.get(); ASSERT_TRUE(false); // exception not thrown } catch (const signalr_exception & e) @@ -447,9 +504,15 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_tran connection_impl::create(create_uri(), trace_level::messages, writer, std::move(http_client), std::make_unique(websocket_client)); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set_exception(exception); + }); + try { - connection->start().get(); + mre.get(); ASSERT_TRUE(false); // exception not thrown } catch (const signalr_exception & e) @@ -490,9 +553,15 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_is_invalid) connection_impl::create(create_uri(), trace_level::messages, writer, std::move(http_client), std::make_unique(websocket_client)); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set_exception(exception); + }); + try { - connection->start().get(); + mre.get(); ASSERT_TRUE(false); // exception not thrown } catch (const std::exception & e) @@ -539,7 +608,13 @@ TEST(connection_impl_start, negotiate_follows_redirect) connection_impl::create(create_uri(), trace_level::messages, writer, std::move(http_client), std::make_unique(websocket_client)); - connection->start().get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); ASSERT_EQ("ws://redirected/?id=f7707523-307d-4cba-9abf-3eef701241e8", connectUrl); } @@ -582,7 +657,13 @@ TEST(connection_impl_start, negotiate_redirect_uses_accessToken) connection_impl::create(create_uri(), trace_level::messages, writer, std::move(http_client), std::make_unique(websocket_client)); - connection->start().get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); ASSERT_EQ("ws://redirected/?id=f7707523-307d-4cba-9abf-3eef701241e8", connectUrl); ASSERT_EQ("Bearer secret", accessToken); @@ -610,9 +691,16 @@ TEST(connection_impl_start, negotiate_fails_after_too_many_redirects) connection_impl::create(create_uri(), trace_level::messages, writer, std::move(http_client), std::make_unique(websocket_client)); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set_exception(exception); + }); + try { - connection->start().get(); + mre.get(); + ASSERT_TRUE(false); } catch (signalr_exception e) { @@ -641,9 +729,15 @@ TEST(connection_impl_start, negotiate_fails_if_ProtocolVersion_in_response) connection_impl::create(create_uri(), trace_level::messages, writer, std::move(http_client), std::make_unique(websocket_client)); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set_exception(exception); + }); + try { - connection->start().get(); + mre.get(); } catch (signalr_exception e) { @@ -682,10 +776,25 @@ TEST(connection_impl_start, negotiate_redirect_does_not_overwrite_url) connection_impl::create(create_uri(), trace_level::messages, writer, std::move(http_client), std::make_unique(websocket_client)); - connection->start().get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + mre.get(); ASSERT_EQ(1, redirectCount); - connection->stop().get(); - connection->start().get(); + + connection->stop([&mre](std::exception_ptr) + { + mre.set(); + }); + mre.get(); + + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + mre.get(); ASSERT_EQ(2, redirectCount); } @@ -724,9 +833,15 @@ TEST(connection_impl_start, negotiate_redirect_uses_own_query_string) auto connection = connection_impl::create(create_uri("a=b&c=d"), trace_level::errors, writer, std::move(http_client), std::make_unique(websocket_client)); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set_exception(exception); + }); + try { - connection->start().get(); + mre.get(); } catch (...) { @@ -757,9 +872,15 @@ TEST(connection_impl_start, start_fails_if_connect_request_times_out) connection_impl::create(create_uri(), trace_level::messages, writer, std::move(http_client), std::make_unique(websocket_client)); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set_exception(exception); + }); + try { - connection->start().get(); + mre.get(); ASSERT_TRUE(false); // exception not thrown } catch (const signalr_exception &e) @@ -782,7 +903,13 @@ TEST(connection_impl_process_response, process_response_logs_messages) }); auto connection = create_connection(websocket_client, writer, trace_level::messages); - connection->start().get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); // Need to give the receive loop time to run std::make_shared()->wait(1000); @@ -809,11 +936,16 @@ TEST(connection_impl_send, message_sent) const std::string message{ "Test message" }; - connection->start() - .then([connection, message]() + auto mre = manual_reset_event(); + connection->start([&mre, connection, message](std::exception_ptr) + { + connection->send(message, [&mre](std::exception_ptr) { - return connection->send(message); - }).get(); + mre.set(); + }); + }); + + mre.get(); ASSERT_EQ(message, actual_message); } @@ -823,9 +955,15 @@ TEST(connection_impl_send, send_throws_if_connection_not_connected) auto connection = connection_impl::create(create_uri(), trace_level::none, std::make_shared()); + auto mre = manual_reset_event(); + connection->send("whatever", [&mre](std::exception_ptr exception) + { + mre.set_exception(exception); + }); + try { - connection->send("whatever").get(); + mre.get(); ASSERT_TRUE(false); // exception expected but not thrown } catch (const signalr_exception &e) @@ -849,13 +987,18 @@ TEST(connection_impl_send, exceptions_from_send_logged_and_propagated) auto connection = create_connection(websocket_client, writer, trace_level::errors); - try + auto mre = manual_reset_event(); + connection->start([&mre, connection](std::exception_ptr exception) { - connection->start() - .then([connection]() + connection->send("Test message", [&mre](std::exception_ptr exception) { - return connection->send("Test message"); - }).get(); + mre.set_exception(exception); + }); + }); + + try + { + mre.get(); ASSERT_TRUE(false); // exception expected but not thrown } @@ -907,7 +1050,13 @@ TEST(connection_impl_set_message_received, callback_invoked_when_message_receive } }); - connection->start().get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); ASSERT_FALSE(message_received_event->wait(5000)); @@ -949,7 +1098,13 @@ TEST(connection_impl_set_message_received, exception_from_callback_caught_and_lo } }); - connection->start().get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); ASSERT_FALSE(message_received_event->wait(5000)); @@ -995,7 +1150,13 @@ TEST(connection_impl_set_message_received, non_std_exception_from_callback_caugh } }); - connection->start().get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); ASSERT_FALSE(message_received_event->wait(5000)); @@ -1012,7 +1173,13 @@ void can_be_set_only_in_disconnected_state(std::function callback) { callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client); - connection->start().get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); try { @@ -1043,7 +1210,13 @@ TEST(connection_impl_stop, stopping_disconnected_connection_is_no_op) { std::shared_ptr writer{ std::make_shared() }; auto connection = connection_impl::create(create_uri(), trace_level::all, writer); - connection->stop().get(); + auto mre = manual_reset_event(); + connection->stop([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); ASSERT_EQ(connection_state::disconnected, connection->get_connection_state()); @@ -1073,19 +1246,35 @@ TEST(connection_impl_stop, stopping_disconnecting_connection_returns_cancelled_t auto connection = create_connection(websocket_client, writer, trace_level::state_changes); - connection->start().get(); - auto stop_task = connection->stop(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); + + connection->stop([&mre](std::exception_ptr exception) + { + mre.set_exception(exception); + }); try { - connection->stop().get(); + auto mre_stop = manual_reset_event(); + connection->stop([&mre_stop](std::exception_ptr exception) + { + mre_stop.set_exception(exception); + }); + + mre_stop.get(); ASSERT_FALSE(true); // exception expected but not thrown } catch (const pplx::task_canceled&) { } close_event.set(); - stop_task.get(); + mre.get(); ASSERT_EQ(connection_state::disconnected, connection->get_connection_state()); @@ -1105,11 +1294,16 @@ TEST(connection_impl_stop, can_start_and_stop_connection) /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client, writer, trace_level::state_changes); - connection->start() - .then([connection]() + auto mre = manual_reset_event(); + connection->start([&mre, connection](std::exception_ptr) + { + connection->stop([&mre](std::exception_ptr) { - return connection->stop(); - }).get(); + mre.set(); + }); + }); + + mre.get(); auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); ASSERT_EQ(4U, log_entries.size()); @@ -1128,15 +1322,27 @@ TEST(connection_impl_stop, can_start_and_stop_connection_multiple_times) /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client, writer, trace_level::state_changes); - connection->start() - .then([connection]() + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); + + connection->stop([&mre](std::exception_ptr) { - return connection->stop(); - }) - .then([connection]() + mre.set(); + }); + + mre.get(); + + connection->start([&mre](std::exception_ptr) { - return connection->start(); - }).get(); + mre.set(); + }); + + mre.get(); } auto memory_writer = std::dynamic_pointer_cast(writer); @@ -1174,7 +1380,13 @@ TEST(connection_impl_stop, dtor_stops_the_connection) }); auto connection = create_connection(websocket_client, writer, trace_level::state_changes); - connection->start().get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); } auto memory_writer = std::dynamic_pointer_cast(writer); @@ -1209,20 +1421,24 @@ TEST(connection_impl_stop, stop_cancels_ongoing_start_request) auto writer = std::shared_ptr{std::make_shared()}; auto connection = create_connection(std::make_shared(), writer, trace_level::all); - auto start_task = connection->start(); - connection->stop().get(); - disconnect_completed_event->set(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set_exception(exception); + }); - start_task.then([](pplx::task t) + connection->stop([disconnect_completed_event](std::exception_ptr) { - try - { - t.get(); - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const pplx::task_canceled &) - { } - }).get(); + disconnect_completed_event->set(); + }); + + try + { + mre.get(); + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const pplx::task_canceled &) + { } ASSERT_EQ(connection_state::disconnected, connection->get_connection_state()); @@ -1257,19 +1473,23 @@ TEST(connection_impl_stop, ongoing_start_request_canceled_if_connection_stopped_ auto connection = connection_impl::create(create_uri(), trace_level::all, writer, std::move(http_client), std::make_unique(websocket_client)); - auto start_task = connection->start(); - connection->stop().get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr exception) + { + mre.set_exception(exception); + }); - start_task.then([](pplx::task t) + connection->stop([](std::exception_ptr) { - try - { - t.get(); - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const pplx::task_canceled &) - { } - }).get(); + }); + + try + { + mre.get(); + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const pplx::task_canceled &) + { } auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); ASSERT_EQ(5U, log_entries.size()) << dump_vector(log_entries); @@ -1289,11 +1509,20 @@ TEST(connection_impl_stop, stop_invokes_disconnected_callback) auto disconnected_invoked = false; connection->set_disconnected([&disconnected_invoked](){ disconnected_invoked = true; }); - connection->start() - .then([connection]() - { - return connection->stop(); - }).get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); + + connection->stop([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); ASSERT_TRUE(disconnected_invoked); } @@ -1320,11 +1549,20 @@ TEST(connection_impl_stop, std_exception_for_disconnected_callback_caught_and_lo connection->set_disconnected([](){ throw std::runtime_error("exception from disconnected"); }); - connection->start() - .then([connection]() - { - return connection->stop(); - }).get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); + + connection->stop([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); ASSERT_EQ(1U, log_entries.size()); @@ -1353,11 +1591,20 @@ TEST(connection_impl_stop, exception_for_disconnected_callback_caught_and_logged connection->set_disconnected([](){ throw 42; }); - connection->start() - .then([connection]() - { - return connection->stop(); - }).get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); + + connection->stop([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); ASSERT_EQ(1U, log_entries.size()); @@ -1400,11 +1647,20 @@ TEST(connection_impl_config, custom_headers_set_in_requests) signalr_client_config.set_http_headers(http_headers); connection->set_client_config(signalr_client_config); - connection->start() - .then([connection]() + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); + + connection->stop([&mre](std::exception_ptr) { - return connection->stop(); - }).get(); + mre.set(); + }); + + mre.get(); ASSERT_EQ(connection_state::disconnected, connection->get_connection_state()); } @@ -1426,7 +1682,12 @@ TEST(connection_impl_change_state, change_state_logs) /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client, writer, trace_level::state_changes); - connection->start().wait(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + mre.get(); auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); ASSERT_FALSE(log_entries.empty()); @@ -1449,22 +1710,16 @@ TEST(connection_id, connection_id_is_set_if_start_fails_but_negotiate_request_su auto connection = create_connection(websocket_client, writer, trace_level::errors); - auto start_task = connection->start() - // this test is not set up to connect successfully so we have to observe exceptions otherwise - // other tests may fail due to an unobserved exception from the outstanding start task - .then([](pplx::task start_task) - { - try - { - start_task.get(); - } - catch (...) - { - } - }); - ASSERT_EQ("", connection->get_connection_id()); - start_task.get(); + + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); + ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", connection->get_connection_id()); } @@ -1477,12 +1732,21 @@ TEST(connection_id, can_get_connection_id_when_connection_in_connected_state) auto connection = create_connection(websocket_client, writer, trace_level::state_changes); std::string connection_id; - connection->start() - .then([connection, &connection_id]() - mutable { - connection_id = connection->get_connection_id(); - return connection->stop(); - }).get(); + auto mre = manual_reset_event(); + connection->start([&mre, &connection_id, connection](std::exception_ptr) + { + connection_id = connection->get_connection_id(); + mre.set(); + }); + + mre.get(); + + connection->stop([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", connection_id); } @@ -1495,11 +1759,20 @@ TEST(connection_id, can_get_connection_id_after_connection_has_stopped) /* receive function */ [](std::function callback){ callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client, writer, trace_level::state_changes); - connection->start() - .then([connection]() - { - return connection->stop(); - }).get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); + + connection->stop([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", connection->get_connection_id()); } @@ -1532,29 +1805,31 @@ TEST(connection_id, connection_id_reset_when_starting_connection) connection_impl::create(create_uri(), trace_level::none, std::make_shared(), std::move(http_client), std::make_unique(websocket_client)); - connection->start() - .then([connection]() - { - return connection->stop(); - }).get(); + auto mre = manual_reset_event(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); + + connection->stop([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", connection->get_connection_id()); fail_http_requests = true; - connection->start() - // this test is not set up to connect successfully so we have to observe exceptions otherwise - // other tests may fail due to an unobserved exception from the outstanding start task - .then([](pplx::task start_task) - { - try - { - start_task.get(); - } - catch (...) - { - } - }).get(); + connection->start([&mre](std::exception_ptr) + { + mre.set(); + }); + + mre.get(); ASSERT_EQ("", connection->get_connection_id()); } diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp index 8739dc38f14c..8d362a2a9974 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp @@ -1,745 +1,745 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - +//// Copyright (c) .NET Foundation. All rights reserved. +//// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// #include "stdafx.h" -#include "test_utils.h" -#include "test_transport_factory.h" -#include "test_http_client.h" -#include "hub_connection_impl.h" -#include "trace_log_writer.h" -#include "memory_log_writer.h" -#include "signalrclient/hub_exception.h" -#include "signalrclient/signalr_exception.h" - -using namespace signalr; - -std::shared_ptr create_hub_connection(std::shared_ptr websocket_client = create_test_websocket_client(), - std::shared_ptr log_writer = std::make_shared(), trace_level trace_level = trace_level::all) -{ - return hub_connection_impl::create(create_uri(), trace_level, log_writer, - create_test_http_client(), std::make_unique(websocket_client)); -} - -TEST(url, negotiate_appended_to_url) -{ - std::string base_urls[] = { "http://fakeuri", "http://fakeuri/" }; - - for (const auto& base_url : base_urls) - { - std::string requested_url; - auto http_client = std::make_unique([&requested_url](const std::string& url, http_request) - { - requested_url = url; - return http_response{ 404, "" }; - }); - - auto hub_connection = hub_connection_impl::create(base_url, trace_level::none, - std::make_shared(), std::move(http_client), - std::make_unique(create_test_websocket_client())); - - try - { - hub_connection->start().get(); - } - catch (const std::exception&) {} - - ASSERT_EQ("http://fakeuri/negotiate", requested_url); - } -} - -TEST(start, start_starts_connection) -{ - auto websocket_client = create_test_websocket_client( - /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); - auto hub_connection = create_hub_connection(websocket_client); - - hub_connection->start().get(); - - ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); -} - -TEST(start, start_sends_handshake) -{ - auto message = std::make_shared(); - auto websocket_client = create_test_websocket_client( - /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, - /* send function */ [message](const std::string& msg, std::function callback) { *message = msg; callback(nullptr); }); - auto hub_connection = create_hub_connection(websocket_client); - - hub_connection->start().get(); - - ASSERT_EQ("{\"protocol\":\"json\",\"version\":1}\x1e", *message); - - ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); -} - -TEST(start, start_waits_for_handshake_response) -{ - pplx::task_completion_event tce; - pplx::task_completion_event tceWaitForSend; - auto websocket_client = create_test_websocket_client( - /* receive function */ [tce, tceWaitForSend](std::function callback) - { - tceWaitForSend.set(); - pplx::task(tce).get(); - callback("{ }\x1e", nullptr); - }); - auto hub_connection = create_hub_connection(websocket_client); - - auto startTask = hub_connection->start(); - pplx::task(tceWaitForSend).get(); - ASSERT_FALSE(startTask.is_done()); - tce.set(); - startTask.get(); - - ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); -} - -TEST(start, start_fails_for_handshake_response_with_error) -{ - auto websocket_client = create_test_websocket_client( - /* receive function */ [](std::function callback) { callback("{\"error\":\"bad things\"}\x1e", nullptr); }); - auto hub_connection = create_hub_connection(websocket_client); - - try - { - hub_connection->start().get(); - ASSERT_TRUE(false); - } - catch (std::exception ex) - { - ASSERT_STREQ("Received an error during handshake: bad things", ex.what()); - } - - ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); -} - -TEST(start, start_fails_if_stop_called_before_handshake_response) -{ - pplx::task_completion_event tce; - pplx::task_completion_event tceWaitForSend; - auto websocket_client = create_test_websocket_client( - /* receive function */ [tce](std::function callback) - { - auto str = pplx::task(tce).get(); - callback(str, nullptr); - }, - /* send function */ [tceWaitForSend](const std::string &, std::function callback) - { - tceWaitForSend.set(); - callback(nullptr); - }); - auto hub_connection = create_hub_connection(websocket_client); - - auto startTask = hub_connection->start(); - pplx::task(tceWaitForSend).get(); - hub_connection->stop(); - - try - { - startTask.get(); - ASSERT_TRUE(false); - } - catch (std::exception ex) - { - ASSERT_STREQ("connection closed while handshake was in progress.", ex.what()); - } - - ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); -} - -TEST(stop, stop_stops_connection) -{ - auto websocket_client = create_test_websocket_client( - /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); - auto hub_connection = create_hub_connection(websocket_client); - - hub_connection->start().get(); - hub_connection->stop().get(); - - ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); -} - -TEST(stop, disconnected_callback_called_when_hub_connection_stops) -{ - auto websocket_client = create_test_websocket_client( - /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); - auto hub_connection = create_hub_connection(websocket_client); - - auto disconnected_invoked = false; - hub_connection->set_disconnected([&disconnected_invoked]() { disconnected_invoked = true; }); - - hub_connection->start().get(); - hub_connection->stop().get(); - - ASSERT_TRUE(disconnected_invoked); -} - -TEST(stop, connection_stopped_when_going_out_of_scope) -{ - std::shared_ptr writer(std::make_shared()); - - { - auto websocket_client = create_test_websocket_client( - /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); - auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::state_changes); - - hub_connection->start().get(); - } - - auto memory_writer = std::dynamic_pointer_cast(writer); - - // The underlying connection_impl will be destroyed when the last reference to shared_ptr holding is released. This can happen - // on a different thread in which case the dtor will be invoked on a different thread so we need to wait for this - // to happen and if it does not the test will fail. There is nothing we can block on. - for (int wait_time_ms = 5; wait_time_ms < 100 && memory_writer->get_log_entries().size() < 4; wait_time_ms <<= 1) - { - std::this_thread::sleep_for(std::chrono::milliseconds(wait_time_ms)); - } - - auto log_entries = memory_writer->get_log_entries(); - ASSERT_EQ(4U, log_entries.size()) << dump_vector(log_entries); - ASSERT_EQ("[state change] disconnected -> connecting\n", remove_date_from_log_entry(log_entries[0])); - ASSERT_EQ("[state change] connecting -> connected\n", remove_date_from_log_entry(log_entries[1])); - ASSERT_EQ("[state change] connected -> disconnecting\n", remove_date_from_log_entry(log_entries[2])); - ASSERT_EQ("[state change] disconnecting -> disconnected\n", remove_date_from_log_entry(log_entries[3])); -} - -TEST(stop, stop_cancels_pending_callbacks) -{ - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number](std::function callback) - mutable { - std::string responses[] - { - "{ }\x1e", - "{}" - }; - - if (call_number < 1) - { - call_number++; - } - - callback(responses[call_number], nullptr); - }); - - auto hub_connection = create_hub_connection(websocket_client); - hub_connection->start().get(); - auto t = hub_connection->invoke("method", json::value::array()); - hub_connection->stop(); - - try - { - t.get(); - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const signalr_exception& e) - { - ASSERT_STREQ("\"connection was stopped before invocation result was received\"", e.what()); - } -} - -TEST(stop, pending_callbacks_finished_if_hub_connections_goes_out_of_scope) -{ - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number](std::function callback) - mutable { - std::string responses[] - { - "{ }\x1e", - "{}" - }; - - if (call_number < 1) - { - call_number++; - } - - callback(responses[call_number], nullptr); - }); - - pplx::task t; - - { - auto hub_connection = create_hub_connection(websocket_client); - hub_connection->start().get(); - t = hub_connection->invoke("method", json::value::array()); - } - - try - { - t.get(); - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const signalr_exception& e) - { - ASSERT_STREQ("\"connection went out of scope before invocation result was received\"", e.what()); - } -} - -TEST(hub_invocation, hub_connection_invokes_users_code_on_hub_invocations) -{ - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number](std::function callback) - mutable { - std::string responses[] - { - "{ }\x1e", - "{ \"type\": 1, \"target\": \"BROADcast\", \"arguments\": [ \"message\", 1 ] }\x1e" - }; - - call_number = std::min(call_number + 1, 1); - - callback(responses[call_number], nullptr); - }); - - auto hub_connection = create_hub_connection(websocket_client); - - auto payload = std::make_shared(); - auto on_broadcast_event = std::make_shared(); - hub_connection->on("broadCAST", [on_broadcast_event, payload](const json::value& message) - { - *payload = utility::conversions::to_utf8string(message.serialize()); - on_broadcast_event->set(); - }); - - hub_connection->start().get(); - ASSERT_FALSE(on_broadcast_event->wait(5000)); - - ASSERT_EQ("[\"message\",1]", *payload); -} - -TEST(send, creates_correct_payload) -{ - std::string payload; - bool handshakeReceived = false; - - auto websocket_client = create_test_websocket_client( - /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, - /* send function */[&payload, &handshakeReceived](const std::string& m, std::function callback) - { - if (handshakeReceived) - { - payload = m; - callback(nullptr); - return; - } - handshakeReceived = true; - callback(nullptr); - }); - - auto hub_connection = create_hub_connection(websocket_client); - hub_connection->start().get(); - - hub_connection->send("method", json::value::array()).get(); - - ASSERT_EQ("{\"arguments\":[],\"target\":\"method\",\"type\":1}\x1e", payload); -} - -TEST(send, does_not_wait_for_server_response) -{ - int call_number = -1; - pplx::task_completion_event waitForSend; - - auto websocket_client = create_test_websocket_client( - /* receive function */ [waitForSend, call_number](std::function callback) mutable - { - std::string responses[] - { - "{ }\x1e", - "{}" - }; - - call_number = std::min(call_number + 1, 1); - - if (call_number == 1) - { - pplx::task(waitForSend).get(); - } - - callback(responses[call_number], nullptr); - }); - - auto hub_connection = create_hub_connection(websocket_client); - hub_connection->start().get(); - - // wont block waiting for server response - hub_connection->send("method", json::value::array()).get(); - waitForSend.set(); -} - -TEST(invoke, creates_correct_payload) -{ - std::string payload; - bool handshakeReceived = false; - - auto websocket_client = create_test_websocket_client( - /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, - /* send function */[&payload, &handshakeReceived](const std::string& m, std::function callback) - { - if (handshakeReceived) - { - payload = m; - callback(std::make_exception_ptr(std::runtime_error("error"))); - return; - } - handshakeReceived = true; - callback(nullptr); - }); - - auto hub_connection = create_hub_connection(websocket_client); - hub_connection->start().get(); - - try - { - hub_connection->invoke("method", json::value::array()).get(); - } - catch (...) - { - // the invoke is not setup to succeed because it's not needed in this test - } - - ASSERT_EQ("{\"arguments\":[],\"invocationId\":\"0\",\"target\":\"method\",\"type\":1}\x1e", payload); -} - -TEST(invoke, callback_not_called_if_send_throws) -{ - bool handshakeReceived = false; - auto websocket_client = create_test_websocket_client( - /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, - /* send function */[handshakeReceived](const std::string&, std::function callback) mutable - { - if (handshakeReceived) - { - callback(std::make_exception_ptr(std::runtime_error("error"))); - return; - } - handshakeReceived = true; - callback(nullptr); - }); - - auto hub_connection = create_hub_connection(websocket_client); - hub_connection->start().get(); - - try - { - hub_connection->invoke("method", json::value::array()).get(); - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const std::runtime_error& e) - { - ASSERT_STREQ("error", e.what()); - } - - // stop completes all outstanding callbacks so if we did not remove a callback when `invoke_void` failed an - // unobserved exception exception would be thrown. Note that this would happen on a different thread and would - // crash the process - hub_connection->stop().get(); -} - -TEST(invoke, invoke_returns_value_returned_from_the_server) -{ - auto callback_registered_event = std::make_shared(); - - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number, callback_registered_event](std::function callback) - mutable { - std::string responses[] - { - "{ }\x1e", - "{ \"type\": 3, \"invocationId\": \"0\", \"result\": \"abc\" }\x1e" - }; - - call_number = std::min(call_number + 1, 1); - - if (call_number > 0) - { - callback_registered_event->wait(); - } - - callback(responses[call_number], nullptr); - }); - - auto hub_connection = create_hub_connection(websocket_client); - auto result = hub_connection->start() - .then([hub_connection, callback_registered_event]() - { - auto t = hub_connection->invoke("method", json::value::array()); - callback_registered_event->set(); - return t; - }).get(); - - ASSERT_EQ(_XPLATSTR("\"abc\""), result.serialize()); -} - -TEST(invoke, invoke_propagates_errors_from_server_as_hub_exceptions) -{ - auto callback_registered_event = std::make_shared(); - - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number, callback_registered_event](std::function callback) - mutable { - std::string responses[] - { - "{ }\x1e", - "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" - }; - - call_number = std::min(call_number + 1, 1); - - if (call_number > 0) - { - callback_registered_event->wait(); - } - - callback(responses[call_number], nullptr); - }); - - auto hub_connection = create_hub_connection(websocket_client); - try - { - hub_connection->start() - .then([hub_connection, callback_registered_event]() - { - auto t = hub_connection->invoke("method", json::value::array()); - callback_registered_event->set(); - return t; - }).get(); - - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const hub_exception& e) - { - ASSERT_STREQ("\"Ooops\"", e.what()); - } -} - -TEST(invoke, unblocks_task_when_server_completes_call) -{ - auto callback_registered_event = std::make_shared(); - - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number, callback_registered_event](std::function callback) - mutable { - std::string responses[] - { - "{ }\x1e", - "{ \"type\": 3, \"invocationId\": \"0\" }\x1e" - }; - - call_number = std::min(call_number + 1, 1); - - if (call_number > 0) - { - callback_registered_event->wait(); - } - - callback(responses[call_number], nullptr); - }); - - auto hub_connection = create_hub_connection(websocket_client); - hub_connection->start() - .then([hub_connection, callback_registered_event]() - { - auto t = hub_connection->invoke("method", json::value::array()); - callback_registered_event->set(); - return t; - }).get(); - - // should not block - ASSERT_TRUE(true); -} - -TEST(receive, logs_if_callback_for_given_id_not_found) -{ - auto message_received_event = std::make_shared(); - auto handshake_sent = std::make_shared(); - - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number, message_received_event, handshake_sent](std::function callback) - mutable { - std::string responses[] - { - "{ }\x1e", - "{ \"type\": 3, \"invocationId\": \"0\" }\x1e", - "{}" - }; - - handshake_sent->wait(1000); - - call_number = std::min(call_number + 1, 2); - - if (call_number > 1) - { - message_received_event->set(); - } - - callback(responses[call_number], nullptr); - }, - [handshake_sent](const std::string&, std::function callback) - { - handshake_sent->set(); - callback(nullptr); - }); - - std::shared_ptr writer(std::make_shared()); - auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::info); - hub_connection->start().get(); - - ASSERT_FALSE(message_received_event->wait(5000)); - - auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); - ASSERT_TRUE(log_entries.size() > 1); - - auto entry = remove_date_from_log_entry(log_entries[2]); - ASSERT_EQ("[info ] no callback found for id: 0\n", entry) << dump_vector(log_entries); -} - -TEST(invoke_void, invoke_creates_runtime_error) -{ - auto callback_registered_event = std::make_shared(); - - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number, callback_registered_event](std::function callback) - mutable { - std::string responses[] - { - "{ }\x1e", - "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" - }; - - call_number = std::min(call_number + 1, 1); - - if (call_number > 0) - { - callback_registered_event->wait(); - } - - callback(responses[call_number], nullptr); - }); - - auto hub_connection = create_hub_connection(websocket_client); - try - { - hub_connection->start() - .then([hub_connection, callback_registered_event]() - { - auto t = hub_connection->invoke("method", json::value::array()); - callback_registered_event->set(); - return t; - }).get(); - - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const hub_exception & e) - { - ASSERT_STREQ("\"Ooops\"", e.what()); - ASSERT_FALSE(callback_registered_event->wait(0)); - } -} - -TEST(connection_id, can_get_connection_id) -{ - auto websocket_client = create_test_websocket_client( - /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); - auto hub_connection = create_hub_connection(websocket_client); - - ASSERT_EQ("", hub_connection->get_connection_id()); - - hub_connection->start().get(); - auto connection_id = hub_connection->get_connection_id(); - hub_connection->stop().get(); - - ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", connection_id); - ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", hub_connection->get_connection_id()); -} - -TEST(on, event_name_must_not_be_empty_string) -{ - auto hub_connection = create_hub_connection(); - try - { - hub_connection->on("", [](const json::value&) {}); - - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const std::invalid_argument& e) - { - ASSERT_STREQ("event_name cannot be empty", e.what()); - } -} - -TEST(on, cannot_register_multiple_handlers_for_event) -{ - auto hub_connection = create_hub_connection(); - hub_connection->on("ping", [](const json::value&) {}); - - try - { - hub_connection->on("ping", [](const json::value&) {}); - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const signalr_exception& e) - { - ASSERT_STREQ("an action for this event has already been registered. event name: ping", e.what()); - } -} - -TEST(on, cannot_register_handler_if_connection_not_in_disconnected_state) -{ - try - { - auto websocket_client = create_test_websocket_client( - /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); - auto hub_connection = create_hub_connection(websocket_client); - - hub_connection->start().get(); - - hub_connection->on("myfunc", [](const web::json::value&) {}); - - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const signalr_exception& e) - { - ASSERT_STREQ("can't register a handler if the connection is in a disconnected state", e.what()); - } -} - -TEST(invoke, invoke_throws_when_the_underlying_connection_is_not_valid) -{ - auto hub_connection = create_hub_connection(); - - try - { - hub_connection->invoke("method", json::value::array()).get(); - ASSERT_TRUE(true); // exception expected but not thrown - } - catch (const signalr_exception& e) - { - ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); - } -} - -TEST(invoke, send_throws_when_the_underlying_connection_is_not_valid) -{ - auto hub_connection = create_hub_connection(); - - try - { - hub_connection->invoke("method", json::value::array()).get(); - ASSERT_TRUE(true); // exception expected but not thrown - } - catch (const signalr_exception& e) - { - ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); - } -} +//#include "test_utils.h" +//#include "test_transport_factory.h" +//#include "test_http_client.h" +//#include "hub_connection_impl.h" +//#include "trace_log_writer.h" +//#include "memory_log_writer.h" +//#include "signalrclient/hub_exception.h" +//#include "signalrclient/signalr_exception.h" +// +//using namespace signalr; +// +//std::shared_ptr create_hub_connection(std::shared_ptr websocket_client = create_test_websocket_client(), +// std::shared_ptr log_writer = std::make_shared(), trace_level trace_level = trace_level::all) +//{ +// return hub_connection_impl::create(create_uri(), trace_level, log_writer, +// create_test_http_client(), std::make_unique(websocket_client)); +//} +// +//TEST(url, negotiate_appended_to_url) +//{ +// std::string base_urls[] = { "http://fakeuri", "http://fakeuri/" }; +// +// for (const auto& base_url : base_urls) +// { +// std::string requested_url; +// auto http_client = std::make_unique([&requested_url](const std::string& url, http_request) +// { +// requested_url = url; +// return http_response{ 404, "" }; +// }); +// +// auto hub_connection = hub_connection_impl::create(base_url, trace_level::none, +// std::make_shared(), std::move(http_client), +// std::make_unique(create_test_websocket_client())); +// +// try +// { +// hub_connection->start().get(); +// } +// catch (const std::exception&) {} +// +// ASSERT_EQ("http://fakeuri/negotiate", requested_url); +// } +//} +// +//TEST(start, start_starts_connection) +//{ +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// hub_connection->start().get(); +// +// ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); +//} +// +//TEST(start, start_sends_handshake) +//{ +// auto message = std::make_shared(); +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, +// /* send function */ [message](const std::string& msg, std::function callback) { *message = msg; callback(nullptr); }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// hub_connection->start().get(); +// +// ASSERT_EQ("{\"protocol\":\"json\",\"version\":1}\x1e", *message); +// +// ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); +//} +// +//TEST(start, start_waits_for_handshake_response) +//{ +// pplx::task_completion_event tce; +// pplx::task_completion_event tceWaitForSend; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [tce, tceWaitForSend](std::function callback) +// { +// tceWaitForSend.set(); +// pplx::task(tce).get(); +// callback("{ }\x1e", nullptr); +// }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// auto startTask = hub_connection->start(); +// pplx::task(tceWaitForSend).get(); +// ASSERT_FALSE(startTask.is_done()); +// tce.set(); +// startTask.get(); +// +// ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); +//} +// +//TEST(start, start_fails_for_handshake_response_with_error) +//{ +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [](std::function callback) { callback("{\"error\":\"bad things\"}\x1e", nullptr); }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// try +// { +// hub_connection->start().get(); +// ASSERT_TRUE(false); +// } +// catch (std::exception ex) +// { +// ASSERT_STREQ("Received an error during handshake: bad things", ex.what()); +// } +// +// ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); +//} +// +//TEST(start, start_fails_if_stop_called_before_handshake_response) +//{ +// pplx::task_completion_event tce; +// pplx::task_completion_event tceWaitForSend; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [tce](std::function callback) +// { +// auto str = pplx::task(tce).get(); +// callback(str, nullptr); +// }, +// /* send function */ [tceWaitForSend](const std::string &, std::function callback) +// { +// tceWaitForSend.set(); +// callback(nullptr); +// }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// auto startTask = hub_connection->start(); +// pplx::task(tceWaitForSend).get(); +// hub_connection->stop(); +// +// try +// { +// startTask.get(); +// ASSERT_TRUE(false); +// } +// catch (std::exception ex) +// { +// ASSERT_STREQ("connection closed while handshake was in progress.", ex.what()); +// } +// +// ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); +//} +// +//TEST(stop, stop_stops_connection) +//{ +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// hub_connection->start().get(); +// hub_connection->stop().get(); +// +// ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); +//} +// +//TEST(stop, disconnected_callback_called_when_hub_connection_stops) +//{ +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// auto disconnected_invoked = false; +// hub_connection->set_disconnected([&disconnected_invoked]() { disconnected_invoked = true; }); +// +// hub_connection->start().get(); +// hub_connection->stop().get(); +// +// ASSERT_TRUE(disconnected_invoked); +//} +// +//TEST(stop, connection_stopped_when_going_out_of_scope) +//{ +// std::shared_ptr writer(std::make_shared()); +// +// { +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); +// auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::state_changes); +// +// hub_connection->start().get(); +// } +// +// auto memory_writer = std::dynamic_pointer_cast(writer); +// +// // The underlying connection_impl will be destroyed when the last reference to shared_ptr holding is released. This can happen +// // on a different thread in which case the dtor will be invoked on a different thread so we need to wait for this +// // to happen and if it does not the test will fail. There is nothing we can block on. +// for (int wait_time_ms = 5; wait_time_ms < 100 && memory_writer->get_log_entries().size() < 4; wait_time_ms <<= 1) +// { +// std::this_thread::sleep_for(std::chrono::milliseconds(wait_time_ms)); +// } +// +// auto log_entries = memory_writer->get_log_entries(); +// ASSERT_EQ(4U, log_entries.size()) << dump_vector(log_entries); +// ASSERT_EQ("[state change] disconnected -> connecting\n", remove_date_from_log_entry(log_entries[0])); +// ASSERT_EQ("[state change] connecting -> connected\n", remove_date_from_log_entry(log_entries[1])); +// ASSERT_EQ("[state change] connected -> disconnecting\n", remove_date_from_log_entry(log_entries[2])); +// ASSERT_EQ("[state change] disconnecting -> disconnected\n", remove_date_from_log_entry(log_entries[3])); +//} +// +//TEST(stop, stop_cancels_pending_callbacks) +//{ +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number](std::function callback) +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{}" +// }; +// +// if (call_number < 1) +// { +// call_number++; +// } +// +// callback(responses[call_number], nullptr); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// hub_connection->start().get(); +// auto t = hub_connection->invoke("method", json::value::array()); +// hub_connection->stop(); +// +// try +// { +// t.get(); +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const signalr_exception& e) +// { +// ASSERT_STREQ("\"connection was stopped before invocation result was received\"", e.what()); +// } +//} +// +//TEST(stop, pending_callbacks_finished_if_hub_connections_goes_out_of_scope) +//{ +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number](std::function callback) +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{}" +// }; +// +// if (call_number < 1) +// { +// call_number++; +// } +// +// callback(responses[call_number], nullptr); +// }); +// +// pplx::task t; +// +// { +// auto hub_connection = create_hub_connection(websocket_client); +// hub_connection->start().get(); +// t = hub_connection->invoke("method", json::value::array()); +// } +// +// try +// { +// t.get(); +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const signalr_exception& e) +// { +// ASSERT_STREQ("\"connection went out of scope before invocation result was received\"", e.what()); +// } +//} +// +//TEST(hub_invocation, hub_connection_invokes_users_code_on_hub_invocations) +//{ +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number](std::function callback) +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{ \"type\": 1, \"target\": \"BROADcast\", \"arguments\": [ \"message\", 1 ] }\x1e" +// }; +// +// call_number = std::min(call_number + 1, 1); +// +// callback(responses[call_number], nullptr); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// +// auto payload = std::make_shared(); +// auto on_broadcast_event = std::make_shared(); +// hub_connection->on("broadCAST", [on_broadcast_event, payload](const json::value& message) +// { +// *payload = utility::conversions::to_utf8string(message.serialize()); +// on_broadcast_event->set(); +// }); +// +// hub_connection->start().get(); +// ASSERT_FALSE(on_broadcast_event->wait(5000)); +// +// ASSERT_EQ("[\"message\",1]", *payload); +//} +// +//TEST(send, creates_correct_payload) +//{ +// std::string payload; +// bool handshakeReceived = false; +// +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, +// /* send function */[&payload, &handshakeReceived](const std::string& m, std::function callback) +// { +// if (handshakeReceived) +// { +// payload = m; +// callback(nullptr); +// return; +// } +// handshakeReceived = true; +// callback(nullptr); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// hub_connection->start().get(); +// +// hub_connection->send("method", json::value::array()).get(); +// +// ASSERT_EQ("{\"arguments\":[],\"target\":\"method\",\"type\":1}\x1e", payload); +//} +// +//TEST(send, does_not_wait_for_server_response) +//{ +// int call_number = -1; +// pplx::task_completion_event waitForSend; +// +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [waitForSend, call_number](std::function callback) mutable +// { +// std::string responses[] +// { +// "{ }\x1e", +// "{}" +// }; +// +// call_number = std::min(call_number + 1, 1); +// +// if (call_number == 1) +// { +// pplx::task(waitForSend).get(); +// } +// +// callback(responses[call_number], nullptr); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// hub_connection->start().get(); +// +// // wont block waiting for server response +// hub_connection->send("method", json::value::array()).get(); +// waitForSend.set(); +//} +// +//TEST(invoke, creates_correct_payload) +//{ +// std::string payload; +// bool handshakeReceived = false; +// +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, +// /* send function */[&payload, &handshakeReceived](const std::string& m, std::function callback) +// { +// if (handshakeReceived) +// { +// payload = m; +// callback(std::make_exception_ptr(std::runtime_error("error"))); +// return; +// } +// handshakeReceived = true; +// callback(nullptr); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// hub_connection->start().get(); +// +// try +// { +// hub_connection->invoke("method", json::value::array()).get(); +// } +// catch (...) +// { +// // the invoke is not setup to succeed because it's not needed in this test +// } +// +// ASSERT_EQ("{\"arguments\":[],\"invocationId\":\"0\",\"target\":\"method\",\"type\":1}\x1e", payload); +//} +// +//TEST(invoke, callback_not_called_if_send_throws) +//{ +// bool handshakeReceived = false; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, +// /* send function */[handshakeReceived](const std::string&, std::function callback) mutable +// { +// if (handshakeReceived) +// { +// callback(std::make_exception_ptr(std::runtime_error("error"))); +// return; +// } +// handshakeReceived = true; +// callback(nullptr); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// hub_connection->start().get(); +// +// try +// { +// hub_connection->invoke("method", json::value::array()).get(); +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const std::runtime_error& e) +// { +// ASSERT_STREQ("error", e.what()); +// } +// +// // stop completes all outstanding callbacks so if we did not remove a callback when `invoke_void` failed an +// // unobserved exception exception would be thrown. Note that this would happen on a different thread and would +// // crash the process +// hub_connection->stop().get(); +//} +// +//TEST(invoke, invoke_returns_value_returned_from_the_server) +//{ +// auto callback_registered_event = std::make_shared(); +// +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number, callback_registered_event](std::function callback) +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{ \"type\": 3, \"invocationId\": \"0\", \"result\": \"abc\" }\x1e" +// }; +// +// call_number = std::min(call_number + 1, 1); +// +// if (call_number > 0) +// { +// callback_registered_event->wait(); +// } +// +// callback(responses[call_number], nullptr); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// auto result = hub_connection->start() +// .then([hub_connection, callback_registered_event]() +// { +// auto t = hub_connection->invoke("method", json::value::array()); +// callback_registered_event->set(); +// return t; +// }).get(); +// +// ASSERT_EQ(_XPLATSTR("\"abc\""), result.serialize()); +//} +// +//TEST(invoke, invoke_propagates_errors_from_server_as_hub_exceptions) +//{ +// auto callback_registered_event = std::make_shared(); +// +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number, callback_registered_event](std::function callback) +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" +// }; +// +// call_number = std::min(call_number + 1, 1); +// +// if (call_number > 0) +// { +// callback_registered_event->wait(); +// } +// +// callback(responses[call_number], nullptr); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// try +// { +// hub_connection->start() +// .then([hub_connection, callback_registered_event]() +// { +// auto t = hub_connection->invoke("method", json::value::array()); +// callback_registered_event->set(); +// return t; +// }).get(); +// +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const hub_exception& e) +// { +// ASSERT_STREQ("\"Ooops\"", e.what()); +// } +//} +// +//TEST(invoke, unblocks_task_when_server_completes_call) +//{ +// auto callback_registered_event = std::make_shared(); +// +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number, callback_registered_event](std::function callback) +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{ \"type\": 3, \"invocationId\": \"0\" }\x1e" +// }; +// +// call_number = std::min(call_number + 1, 1); +// +// if (call_number > 0) +// { +// callback_registered_event->wait(); +// } +// +// callback(responses[call_number], nullptr); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// hub_connection->start() +// .then([hub_connection, callback_registered_event]() +// { +// auto t = hub_connection->invoke("method", json::value::array()); +// callback_registered_event->set(); +// return t; +// }).get(); +// +// // should not block +// ASSERT_TRUE(true); +//} +// +//TEST(receive, logs_if_callback_for_given_id_not_found) +//{ +// auto message_received_event = std::make_shared(); +// auto handshake_sent = std::make_shared(); +// +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number, message_received_event, handshake_sent](std::function callback) +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{ \"type\": 3, \"invocationId\": \"0\" }\x1e", +// "{}" +// }; +// +// handshake_sent->wait(1000); +// +// call_number = std::min(call_number + 1, 2); +// +// if (call_number > 1) +// { +// message_received_event->set(); +// } +// +// callback(responses[call_number], nullptr); +// }, +// [handshake_sent](const std::string&, std::function callback) +// { +// handshake_sent->set(); +// callback(nullptr); +// }); +// +// std::shared_ptr writer(std::make_shared()); +// auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::info); +// hub_connection->start().get(); +// +// ASSERT_FALSE(message_received_event->wait(5000)); +// +// auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); +// ASSERT_TRUE(log_entries.size() > 1); +// +// auto entry = remove_date_from_log_entry(log_entries[2]); +// ASSERT_EQ("[info ] no callback found for id: 0\n", entry) << dump_vector(log_entries); +//} +// +//TEST(invoke_void, invoke_creates_runtime_error) +//{ +// auto callback_registered_event = std::make_shared(); +// +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number, callback_registered_event](std::function callback) +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" +// }; +// +// call_number = std::min(call_number + 1, 1); +// +// if (call_number > 0) +// { +// callback_registered_event->wait(); +// } +// +// callback(responses[call_number], nullptr); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// try +// { +// hub_connection->start() +// .then([hub_connection, callback_registered_event]() +// { +// auto t = hub_connection->invoke("method", json::value::array()); +// callback_registered_event->set(); +// return t; +// }).get(); +// +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const hub_exception & e) +// { +// ASSERT_STREQ("\"Ooops\"", e.what()); +// ASSERT_FALSE(callback_registered_event->wait(0)); +// } +//} +// +//TEST(connection_id, can_get_connection_id) +//{ +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// ASSERT_EQ("", hub_connection->get_connection_id()); +// +// hub_connection->start().get(); +// auto connection_id = hub_connection->get_connection_id(); +// hub_connection->stop().get(); +// +// ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", connection_id); +// ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", hub_connection->get_connection_id()); +//} +// +//TEST(on, event_name_must_not_be_empty_string) +//{ +// auto hub_connection = create_hub_connection(); +// try +// { +// hub_connection->on("", [](const json::value&) {}); +// +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const std::invalid_argument& e) +// { +// ASSERT_STREQ("event_name cannot be empty", e.what()); +// } +//} +// +//TEST(on, cannot_register_multiple_handlers_for_event) +//{ +// auto hub_connection = create_hub_connection(); +// hub_connection->on("ping", [](const json::value&) {}); +// +// try +// { +// hub_connection->on("ping", [](const json::value&) {}); +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const signalr_exception& e) +// { +// ASSERT_STREQ("an action for this event has already been registered. event name: ping", e.what()); +// } +//} +// +//TEST(on, cannot_register_handler_if_connection_not_in_disconnected_state) +//{ +// try +// { +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// hub_connection->start().get(); +// +// hub_connection->on("myfunc", [](const web::json::value&) {}); +// +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const signalr_exception& e) +// { +// ASSERT_STREQ("can't register a handler if the connection is in a disconnected state", e.what()); +// } +//} +// +//TEST(invoke, invoke_throws_when_the_underlying_connection_is_not_valid) +//{ +// auto hub_connection = create_hub_connection(); +// +// try +// { +// hub_connection->invoke("method", json::value::array()).get(); +// ASSERT_TRUE(true); // exception expected but not thrown +// } +// catch (const signalr_exception& e) +// { +// ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); +// } +//} +// +//TEST(invoke, send_throws_when_the_underlying_connection_is_not_valid) +//{ +// auto hub_connection = create_hub_connection(); +// +// try +// { +// hub_connection->invoke("method", json::value::array()).get(); +// ASSERT_TRUE(true); // exception expected but not thrown +// } +// catch (const signalr_exception& e) +// { +// ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); +// } +//} diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h index eb7d98c25625..a34983874e1f 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h @@ -33,12 +33,12 @@ class manual_reset_event m_promise.set_value(value); } - void set_exception(std::exception exception) + void set(const std::exception& exception) { m_promise.set_exception(std::make_exception_ptr(exception)); } - void set_exception(std::exception_ptr exception) + void set(std::exception_ptr exception) { m_promise.set_exception(exception); } @@ -71,14 +71,21 @@ class manual_reset_event m_promise.set_value(); } - void set_exception(std::exception exception) + void set(const std::exception& exception) { m_promise.set_exception(std::make_exception_ptr(exception)); } - void set_exception(std::exception_ptr exception) + void set(std::exception_ptr exception) { - m_promise.set_exception(exception); + if (exception != nullptr) + { + m_promise.set_exception(exception); + } + else + { + m_promise.set_value(); + } } void get() From 4bbaf1c39b1dd996370651411b2315977a7c4693 Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Wed, 20 Mar 2019 23:09:54 -0700 Subject: [PATCH 2/9] stash --- .../cpp/src/signalrclient/connection_impl.cpp | 5 +- .../connection_impl_tests.cpp | 239 +++++++++++------- .../websocket_transport_tests.cpp | 107 ++++---- 3 files changed, 196 insertions(+), 155 deletions(-) diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp index 882a625aba29..0b72fa07dfb5 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp @@ -449,7 +449,6 @@ namespace signalr try { connection->m_disconnected(); - callback(nullptr); } catch (const std::exception &e) { @@ -457,15 +456,15 @@ namespace signalr trace_level::errors, std::string("disconnected callback threw an exception: ") .append(e.what())); - callback(std::make_exception_ptr(e)); } catch (...) { connection->m_logger.log( trace_level::errors, std::string("disconnected callback threw an unknown exception")); - callback(std::current_exception()); } + + callback(nullptr); }); } diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp index 20d575f22425..afc68ee0c94a 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp @@ -79,7 +79,13 @@ TEST(connection_impl_start, connection_state_is_connecting_when_connection_is_be }); ASSERT_EQ(connection->get_connection_state(), connection_state::connecting); - mre.get(); + + try + { + mre.get(); + ASSERT_TRUE(false); + } + catch (...) { } } TEST(connection_impl_start, connection_state_is_connected_when_connection_established_succesfully) @@ -113,7 +119,12 @@ TEST(connection_impl_start, connection_state_is_disconnected_when_connection_can mre.set(exception); }); - mre.get(); + try + { + mre.get(); + ASSERT_TRUE(false); + } + catch (...) { } ASSERT_EQ(connection->get_connection_state(), connection_state::disconnected); } @@ -168,7 +179,12 @@ TEST(connection_impl_start, start_sets_id_query_string) mre.set(exception); }); - mre.get(); + try + { + mre.get(); + ASSERT_TRUE(false); + } + catch (...) { } ASSERT_EQ("id=f7707523-307d-4cba-9abf-3eef701241e8", query_string); } @@ -195,7 +211,12 @@ TEST(connection_impl_start, start_appends_id_query_string) mre.set(exception); }); - mre.get(); + try + { + mre.get(); + ASSERT_TRUE(false); + } + catch (...) { } ASSERT_EQ("a=b&c=d&id=f7707523-307d-4cba-9abf-3eef701241e8", query_string); } @@ -219,7 +240,12 @@ TEST(connection_impl_start, start_logs_exceptions) mre.set(exception); }); - mre.get(); + try + { + mre.get(); + ASSERT_TRUE(false); + } + catch (...) { } auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); ASSERT_FALSE(log_entries.empty()); @@ -309,16 +335,16 @@ TEST(connection_impl_send, send_fails_if_transport_fails_when_receiving_messages auto connection = create_connection(websocket_client, writer, trace_level::errors); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); connection->send("message", [&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); try @@ -362,7 +388,7 @@ TEST(connection_impl_start, start_fails_if_negotiate_request_fails) auto mre = manual_reset_event(); connection->start([&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); try @@ -409,7 +435,7 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_has_error) auto mre = manual_reset_event(); connection->start([&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); try @@ -458,7 +484,7 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_webs auto mre = manual_reset_event(); connection->start([&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); try @@ -507,7 +533,7 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_tran auto mre = manual_reset_event(); connection->start([&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); try @@ -556,7 +582,7 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_is_invalid) auto mre = manual_reset_event(); connection->start([&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); try @@ -609,9 +635,9 @@ TEST(connection_impl_start, negotiate_follows_redirect) std::move(http_client), std::make_unique(websocket_client)); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -658,9 +684,9 @@ TEST(connection_impl_start, negotiate_redirect_uses_accessToken) std::move(http_client), std::make_unique(websocket_client)); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -694,7 +720,7 @@ TEST(connection_impl_start, negotiate_fails_after_too_many_redirects) auto mre = manual_reset_event(); connection->start([&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); try @@ -732,7 +758,7 @@ TEST(connection_impl_start, negotiate_fails_if_ProtocolVersion_in_response) auto mre = manual_reset_event(); connection->start([&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); try @@ -777,9 +803,9 @@ TEST(connection_impl_start, negotiate_redirect_does_not_overwrite_url) std::move(http_client), std::make_unique(websocket_client)); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); ASSERT_EQ(1, redirectCount); @@ -836,7 +862,7 @@ TEST(connection_impl_start, negotiate_redirect_uses_own_query_string) auto mre = manual_reset_event(); connection->start([&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); try @@ -875,7 +901,7 @@ TEST(connection_impl_start, start_fails_if_connect_request_times_out) auto mre = manual_reset_event(); connection->start([&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); try @@ -904,9 +930,9 @@ TEST(connection_impl_process_response, process_response_logs_messages) auto connection = create_connection(websocket_client, writer, trace_level::messages); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -937,12 +963,16 @@ TEST(connection_impl_send, message_sent) const std::string message{ "Test message" }; auto mre = manual_reset_event(); - connection->start([&mre, connection, message](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - connection->send(message, [&mre](std::exception_ptr) - { - mre.set(); - }); + mre.set(exception); + }); + + mre.get(); + + connection->send(message, [&mre](std::exception_ptr exception) + { + mre.set(exception); }); mre.get(); @@ -958,7 +988,7 @@ TEST(connection_impl_send, send_throws_if_connection_not_connected) auto mre = manual_reset_event(); connection->send("whatever", [&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); try @@ -988,12 +1018,16 @@ TEST(connection_impl_send, exceptions_from_send_logged_and_propagated) auto connection = create_connection(websocket_client, writer, trace_level::errors); auto mre = manual_reset_event(); - connection->start([&mre, connection](std::exception_ptr exception) + connection->start([&mre](std::exception_ptr exception) { - connection->send("Test message", [&mre](std::exception_ptr exception) - { - mre.set_exception(exception); - }); + mre.set(exception); + }); + + mre.get(); + + connection->send("Test message", [&mre](std::exception_ptr exception) + { + mre.set(exception); }); try @@ -1051,9 +1085,9 @@ TEST(connection_impl_set_message_received, callback_invoked_when_message_receive }); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -1099,9 +1133,9 @@ TEST(connection_impl_set_message_received, exception_from_callback_caught_and_lo }); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -1151,9 +1185,9 @@ TEST(connection_impl_set_message_received, non_std_exception_from_callback_caugh }); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -1174,9 +1208,9 @@ void can_be_set_only_in_disconnected_state(std::function(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -1211,9 +1245,9 @@ TEST(connection_impl_stop, stopping_disconnected_connection_is_no_op) std::shared_ptr writer{ std::make_shared() }; auto connection = connection_impl::create(create_uri(), trace_level::all, writer); auto mre = manual_reset_event(); - connection->stop([&mre](std::exception_ptr) + connection->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -1247,16 +1281,16 @@ TEST(connection_impl_stop, stopping_disconnecting_connection_returns_cancelled_t auto connection = create_connection(websocket_client, writer, trace_level::state_changes); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); connection->stop([&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); try @@ -1264,7 +1298,7 @@ TEST(connection_impl_stop, stopping_disconnecting_connection_returns_cancelled_t auto mre_stop = manual_reset_event(); connection->stop([&mre_stop](std::exception_ptr exception) { - mre_stop.set_exception(exception); + mre_stop.set(exception); }); mre_stop.get(); @@ -1295,12 +1329,16 @@ TEST(connection_impl_stop, can_start_and_stop_connection) auto connection = create_connection(websocket_client, writer, trace_level::state_changes); auto mre = manual_reset_event(); - connection->start([&mre, connection](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - connection->stop([&mre](std::exception_ptr) - { - mre.set(); - }); + mre.set(exception); + }); + + mre.get(); + + connection->stop([&mre](std::exception_ptr exception) + { + mre.set(exception); }); mre.get(); @@ -1323,23 +1361,23 @@ TEST(connection_impl_stop, can_start_and_stop_connection_multiple_times) auto connection = create_connection(websocket_client, writer, trace_level::state_changes); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); - connection->stop([&mre](std::exception_ptr) + connection->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -1381,9 +1419,9 @@ TEST(connection_impl_stop, dtor_stops_the_connection) auto connection = create_connection(websocket_client, writer, trace_level::state_changes); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -1424,7 +1462,7 @@ TEST(connection_impl_stop, stop_cancels_ongoing_start_request) auto mre = manual_reset_event(); connection->start([&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); connection->stop([disconnect_completed_event](std::exception_ptr) @@ -1476,7 +1514,7 @@ TEST(connection_impl_stop, ongoing_start_request_canceled_if_connection_stopped_ auto mre = manual_reset_event(); connection->start([&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); connection->stop([](std::exception_ptr) @@ -1510,16 +1548,16 @@ TEST(connection_impl_stop, stop_invokes_disconnected_callback) connection->set_disconnected([&disconnected_invoked](){ disconnected_invoked = true; }); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); - connection->stop([&mre](std::exception_ptr) + connection->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -1550,16 +1588,16 @@ TEST(connection_impl_stop, std_exception_for_disconnected_callback_caught_and_lo connection->set_disconnected([](){ throw std::runtime_error("exception from disconnected"); }); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); - connection->stop([&mre](std::exception_ptr) + connection->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -1592,16 +1630,16 @@ TEST(connection_impl_stop, exception_for_disconnected_callback_caught_and_logged connection->set_disconnected([](){ throw 42; }); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); - connection->stop([&mre](std::exception_ptr) + connection->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -1648,16 +1686,16 @@ TEST(connection_impl_config, custom_headers_set_in_requests) connection->set_client_config(signalr_client_config); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); - connection->stop([&mre](std::exception_ptr) + connection->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -1683,9 +1721,9 @@ TEST(connection_impl_change_state, change_state_logs) auto connection = create_connection(websocket_client, writer, trace_level::state_changes); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -1713,12 +1751,17 @@ TEST(connection_id, connection_id_is_set_if_start_fails_but_negotiate_request_su ASSERT_EQ("", connection->get_connection_id()); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); - mre.get(); + try + { + mre.get(); + ASSERT_TRUE(false); + } + catch (...) { } ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", connection->get_connection_id()); } @@ -1733,17 +1776,17 @@ TEST(connection_id, can_get_connection_id_when_connection_in_connected_state) std::string connection_id; auto mre = manual_reset_event(); - connection->start([&mre, &connection_id, connection](std::exception_ptr) + connection->start([&mre, &connection_id, connection](std::exception_ptr exception) { connection_id = connection->get_connection_id(); - mre.set(); + mre.set(exception); }); mre.get(); - connection->stop([&mre](std::exception_ptr) + connection->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -1760,16 +1803,16 @@ TEST(connection_id, can_get_connection_id_after_connection_has_stopped) auto connection = create_connection(websocket_client, writer, trace_level::state_changes); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); - connection->stop([&mre](std::exception_ptr) + connection->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -1806,16 +1849,16 @@ TEST(connection_id, connection_id_reset_when_starting_connection) std::move(http_client), std::make_unique(websocket_client)); auto mre = manual_reset_event(); - connection->start([&mre](std::exception_ptr) + connection->start([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); - connection->stop([&mre](std::exception_ptr) + connection->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp index 03114e44b18a..543329347ddc 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp @@ -34,9 +34,9 @@ TEST(websocket_transport_connect, connect_connects_and_starts_receive_loop) auto ws_transport = websocket_transport::create([&](){ return client; }, logger(writer, trace_level::info)); auto mre = manual_reset_event(); - ws_transport->start("ws://fakeuri.org/connect?param=42", transfer_format::text, [&mre](std::exception_ptr) + ws_transport->start("ws://fakeuri.org/connect?param=42", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -66,7 +66,7 @@ TEST(websocket_transport_connect, connect_propagates_exceptions) auto mre = manual_reset_event(); ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); mre.get(); ASSERT_TRUE(false); @@ -91,7 +91,7 @@ TEST(websocket_transport_connect, connect_logs_exceptions) auto mre = manual_reset_event(); ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); try { @@ -116,9 +116,9 @@ TEST(websocket_transport_connect, cannot_call_connect_on_already_connected_trans auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); auto mre = manual_reset_event(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -126,7 +126,7 @@ TEST(websocket_transport_connect, cannot_call_connect_on_already_connected_trans { ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set_exception(exception); + mre.set(exception); }); mre.get(); ASSERT_TRUE(false); @@ -143,21 +143,21 @@ TEST(websocket_transport_connect, can_connect_after_disconnecting) auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); auto mre = manual_reset_event(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); - ws_transport->stop([&mre](std::exception_ptr) + ws_transport->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); } @@ -177,15 +177,15 @@ TEST(websocket_transport_send, send_creates_and_sends_websocket_messages) auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); auto mre = manual_reset_event(); - ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr) + ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); - ws_transport->send("ABC", [&mre](std::exception_ptr) + ws_transport->send("ABC", [&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -207,15 +207,15 @@ TEST(websocket_transport_disconnect, disconnect_closes_websocket) auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); auto mre = manual_reset_event(); - ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr) + ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); - ws_transport->stop([&mre](std::exception_ptr) + ws_transport->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -236,22 +236,15 @@ TEST(websocket_transport_stop, propogates_exception_from_close) auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); auto mre = manual_reset_event(); - ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr) + ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); ws_transport->stop([&mre](std::exception_ptr exception) { - if (exception) - { - mre.set_exception(exception); - } - else - { - mre.set(); - } + mre.set(exception); }); try { @@ -276,17 +269,23 @@ TEST(websocket_transport_disconnect, disconnect_logs_exceptions) auto ws_transport = websocket_transport::create([&](){ return client; }, logger(writer, trace_level::errors)); auto mre = manual_reset_event(); - ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr) + ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); - ws_transport->stop([&mre](std::exception_ptr) + ws_transport->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); - mre.get(); + + try + { + mre.get(); + ASSERT_TRUE(false); + } + catch (...) {} auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); @@ -344,26 +343,26 @@ TEST(websocket_transport_disconnect, receive_not_called_after_disconnect) pplx::create_task(receive_task_started_tce).get(); - ws_transport->stop([&mre](std::exception_ptr) + ws_transport->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); receive_task_tce = pplx::task_completion_event(); receive_task_started_tce = pplx::task_completion_event(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); pplx::create_task(receive_task_started_tce).get(); - ws_transport->stop([&mre](std::exception_ptr) + ws_transport->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -385,9 +384,9 @@ TEST(websocket_transport_disconnect, disconnect_is_no_op_if_transport_not_starte auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); auto mre = manual_reset_event(); - ws_transport->stop([&mre](std::exception_ptr) + ws_transport->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -411,15 +410,15 @@ TEST(websocket_transport_disconnect, exceptions_from_outstanding_receive_task_ob auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); auto mre = manual_reset_event(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); - ws_transport->stop([&mre](std::exception_ptr) + ws_transport->stop([&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -472,9 +471,9 @@ void receive_loop_logs_exception_runner(const T& e, const std::string& expected_ auto ws_transport = websocket_transport::create([&](){ return client; }, logger(writer, trace_level)); auto mre = manual_reset_event(); - ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr) + ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -512,9 +511,9 @@ TEST(websocket_transport_receive_loop, process_response_callback_called_when_mes ws_transport->on_receive(process_response); auto mre = manual_reset_event(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); @@ -558,9 +557,9 @@ TEST(websocket_transport_receive_loop, error_callback_called_when_exception_thro ws_transport->on_close(error_callback); auto mre = manual_reset_event(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) { - mre.set(); + mre.set(exception); }); mre.get(); From 68962564013407c816bb3f9ff7cff0dec6a12e4d Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Thu, 21 Mar 2019 12:47:51 -0700 Subject: [PATCH 3/9] boom --- .../src/signalrclient/hub_connection_impl.cpp | 91 +- .../hub_connection_impl_tests.cpp | 1723 ++++++++++------- 2 files changed, 1033 insertions(+), 781 deletions(-) diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp index 4c7fc112dc71..74c8147f13dc 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp @@ -108,7 +108,7 @@ namespace signalr m_handshakeTask = pplx::task_completion_event(); m_handshakeReceived = false; std::weak_ptr weak_connection = shared_from_this(); - return m_connection->start([weak_connection, callback](std::exception_ptr exception) + return m_connection->start([weak_connection, callback](std::exception_ptr start_exception) { //startTask.get(); auto connection = weak_connection.lock(); @@ -119,6 +119,31 @@ namespace signalr return; } + if (start_exception) + { + connection->m_connection->stop([start_exception, callback, connection](std::exception_ptr exception) + { + try + { + pplx::task(connection->m_handshakeTask).get(); + } + catch (...) {} + if (start_exception) + { + callback(start_exception); + } + else if (exception) + { + callback(exception); + } + else + { + callback(nullptr); + } + }); + return; + } + connection->m_connection->send("{\"protocol\":\"json\",\"version\":1}\x1e", [weak_connection, callback](std::exception_ptr exception) { auto connection = weak_connection.lock(); @@ -128,29 +153,27 @@ namespace signalr callback(std::make_exception_ptr(signalr_exception("the hub connection has been deconstructed"))); return; } - //previous_task.get(); - //return pplx::task(connection->m_handshakeTask); - }); - /* .then([weak_connection](pplx::task previous_task) + + if (exception) { - try - { - previous_task.get(); - return previous_task; - } - catch (std::exception e) + callback(exception); + return; + } + + try + { + pplx::task(connection->m_handshakeTask).get(); + callback(nullptr); + } + catch (...) + { + auto handshake_exception = std::current_exception(); + connection->m_connection->stop([callback, handshake_exception](std::exception_ptr) { - auto connection = weak_connection.lock(); - if (connection) - { - return connection->m_connection->stop() - .then([e]() { - throw e; - }); - } - throw e; - } - });*/ + callback(handshake_exception); + }); + } + }); }); } @@ -280,29 +303,21 @@ namespace signalr { _ASSERTE(arguments.is_array()); - pplx::task_completion_event tce; - const auto callback_id = m_callback_manager.register_callback( - create_hub_invocation_callback(m_logger, [tce](const json::value& result) { tce.set(result); }, - [tce](const std::exception_ptr e) { tce.set_exception(e); })); + create_hub_invocation_callback(m_logger, [callback](const json::value& result) { callback(result, nullptr); }, + [callback](const std::exception_ptr e) { callback(json::value(), e); })); invoke_hub_method(method_name, arguments, callback_id, nullptr, - [tce](const std::exception_ptr e){ tce.set_exception(e); }); - - //return pplx::create_task(tce); + [callback](const std::exception_ptr e){ callback(json::value(), e); }); } void hub_connection_impl::send(const std::string& method_name, const json::value& arguments, std::function callback) { _ASSERTE(arguments.is_array()); - pplx::task_completion_event tce; - invoke_hub_method(method_name, arguments, "", - [tce]() { tce.set(); }, - [tce](const std::exception_ptr e){ tce.set_exception(e); }); - - //return pplx::create_task(tce); + [callback]() { callback(nullptr); }, + [callback](const std::exception_ptr e){ callback(e); }); } void hub_connection_impl::invoke_hub_method(const std::string& method_name, const json::value& arguments, @@ -382,8 +397,10 @@ namespace signalr std::make_exception_ptr( hub_exception(utility::conversions::to_utf8string(message.at(_XPLATSTR("error")).serialize())))); } - - set_result(json::value::null()); + else + { + set_result(json::value::null()); + } }; } } diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp index 8d362a2a9974..39e146f915dd 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp @@ -1,745 +1,980 @@ -//// Copyright (c) .NET Foundation. All rights reserved. -//// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + #include "stdafx.h" -//#include "test_utils.h" -//#include "test_transport_factory.h" -//#include "test_http_client.h" -//#include "hub_connection_impl.h" -//#include "trace_log_writer.h" -//#include "memory_log_writer.h" -//#include "signalrclient/hub_exception.h" -//#include "signalrclient/signalr_exception.h" -// -//using namespace signalr; -// -//std::shared_ptr create_hub_connection(std::shared_ptr websocket_client = create_test_websocket_client(), -// std::shared_ptr log_writer = std::make_shared(), trace_level trace_level = trace_level::all) -//{ -// return hub_connection_impl::create(create_uri(), trace_level, log_writer, -// create_test_http_client(), std::make_unique(websocket_client)); -//} -// -//TEST(url, negotiate_appended_to_url) -//{ -// std::string base_urls[] = { "http://fakeuri", "http://fakeuri/" }; -// -// for (const auto& base_url : base_urls) -// { -// std::string requested_url; -// auto http_client = std::make_unique([&requested_url](const std::string& url, http_request) -// { -// requested_url = url; -// return http_response{ 404, "" }; -// }); -// -// auto hub_connection = hub_connection_impl::create(base_url, trace_level::none, -// std::make_shared(), std::move(http_client), -// std::make_unique(create_test_websocket_client())); -// -// try -// { -// hub_connection->start().get(); -// } -// catch (const std::exception&) {} -// -// ASSERT_EQ("http://fakeuri/negotiate", requested_url); -// } -//} -// -//TEST(start, start_starts_connection) -//{ -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// hub_connection->start().get(); -// -// ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); -//} -// -//TEST(start, start_sends_handshake) -//{ -// auto message = std::make_shared(); -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, -// /* send function */ [message](const std::string& msg, std::function callback) { *message = msg; callback(nullptr); }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// hub_connection->start().get(); -// -// ASSERT_EQ("{\"protocol\":\"json\",\"version\":1}\x1e", *message); -// -// ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); -//} -// -//TEST(start, start_waits_for_handshake_response) -//{ -// pplx::task_completion_event tce; -// pplx::task_completion_event tceWaitForSend; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [tce, tceWaitForSend](std::function callback) -// { -// tceWaitForSend.set(); -// pplx::task(tce).get(); -// callback("{ }\x1e", nullptr); -// }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// auto startTask = hub_connection->start(); -// pplx::task(tceWaitForSend).get(); -// ASSERT_FALSE(startTask.is_done()); -// tce.set(); -// startTask.get(); -// -// ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); -//} -// -//TEST(start, start_fails_for_handshake_response_with_error) -//{ -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [](std::function callback) { callback("{\"error\":\"bad things\"}\x1e", nullptr); }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// try -// { -// hub_connection->start().get(); -// ASSERT_TRUE(false); -// } -// catch (std::exception ex) -// { -// ASSERT_STREQ("Received an error during handshake: bad things", ex.what()); -// } -// -// ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); -//} -// -//TEST(start, start_fails_if_stop_called_before_handshake_response) -//{ -// pplx::task_completion_event tce; -// pplx::task_completion_event tceWaitForSend; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [tce](std::function callback) -// { -// auto str = pplx::task(tce).get(); -// callback(str, nullptr); -// }, -// /* send function */ [tceWaitForSend](const std::string &, std::function callback) -// { -// tceWaitForSend.set(); -// callback(nullptr); -// }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// auto startTask = hub_connection->start(); -// pplx::task(tceWaitForSend).get(); -// hub_connection->stop(); -// -// try -// { -// startTask.get(); -// ASSERT_TRUE(false); -// } -// catch (std::exception ex) -// { -// ASSERT_STREQ("connection closed while handshake was in progress.", ex.what()); -// } -// -// ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); -//} -// -//TEST(stop, stop_stops_connection) -//{ -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// hub_connection->start().get(); -// hub_connection->stop().get(); -// -// ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); -//} -// -//TEST(stop, disconnected_callback_called_when_hub_connection_stops) -//{ -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// auto disconnected_invoked = false; -// hub_connection->set_disconnected([&disconnected_invoked]() { disconnected_invoked = true; }); -// -// hub_connection->start().get(); -// hub_connection->stop().get(); -// -// ASSERT_TRUE(disconnected_invoked); -//} -// -//TEST(stop, connection_stopped_when_going_out_of_scope) -//{ -// std::shared_ptr writer(std::make_shared()); -// -// { -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); -// auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::state_changes); -// -// hub_connection->start().get(); -// } -// -// auto memory_writer = std::dynamic_pointer_cast(writer); -// -// // The underlying connection_impl will be destroyed when the last reference to shared_ptr holding is released. This can happen -// // on a different thread in which case the dtor will be invoked on a different thread so we need to wait for this -// // to happen and if it does not the test will fail. There is nothing we can block on. -// for (int wait_time_ms = 5; wait_time_ms < 100 && memory_writer->get_log_entries().size() < 4; wait_time_ms <<= 1) -// { -// std::this_thread::sleep_for(std::chrono::milliseconds(wait_time_ms)); -// } -// -// auto log_entries = memory_writer->get_log_entries(); -// ASSERT_EQ(4U, log_entries.size()) << dump_vector(log_entries); -// ASSERT_EQ("[state change] disconnected -> connecting\n", remove_date_from_log_entry(log_entries[0])); -// ASSERT_EQ("[state change] connecting -> connected\n", remove_date_from_log_entry(log_entries[1])); -// ASSERT_EQ("[state change] connected -> disconnecting\n", remove_date_from_log_entry(log_entries[2])); -// ASSERT_EQ("[state change] disconnecting -> disconnected\n", remove_date_from_log_entry(log_entries[3])); -//} -// -//TEST(stop, stop_cancels_pending_callbacks) -//{ -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number](std::function callback) -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{}" -// }; -// -// if (call_number < 1) -// { -// call_number++; -// } -// -// callback(responses[call_number], nullptr); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// hub_connection->start().get(); -// auto t = hub_connection->invoke("method", json::value::array()); -// hub_connection->stop(); -// -// try -// { -// t.get(); -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const signalr_exception& e) -// { -// ASSERT_STREQ("\"connection was stopped before invocation result was received\"", e.what()); -// } -//} -// -//TEST(stop, pending_callbacks_finished_if_hub_connections_goes_out_of_scope) -//{ -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number](std::function callback) -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{}" -// }; -// -// if (call_number < 1) -// { -// call_number++; -// } -// -// callback(responses[call_number], nullptr); -// }); -// -// pplx::task t; -// -// { -// auto hub_connection = create_hub_connection(websocket_client); -// hub_connection->start().get(); -// t = hub_connection->invoke("method", json::value::array()); -// } -// -// try -// { -// t.get(); -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const signalr_exception& e) -// { -// ASSERT_STREQ("\"connection went out of scope before invocation result was received\"", e.what()); -// } -//} -// -//TEST(hub_invocation, hub_connection_invokes_users_code_on_hub_invocations) -//{ -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number](std::function callback) -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{ \"type\": 1, \"target\": \"BROADcast\", \"arguments\": [ \"message\", 1 ] }\x1e" -// }; -// -// call_number = std::min(call_number + 1, 1); -// -// callback(responses[call_number], nullptr); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// -// auto payload = std::make_shared(); -// auto on_broadcast_event = std::make_shared(); -// hub_connection->on("broadCAST", [on_broadcast_event, payload](const json::value& message) -// { -// *payload = utility::conversions::to_utf8string(message.serialize()); -// on_broadcast_event->set(); -// }); -// -// hub_connection->start().get(); -// ASSERT_FALSE(on_broadcast_event->wait(5000)); -// -// ASSERT_EQ("[\"message\",1]", *payload); -//} -// -//TEST(send, creates_correct_payload) -//{ -// std::string payload; -// bool handshakeReceived = false; -// -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, -// /* send function */[&payload, &handshakeReceived](const std::string& m, std::function callback) -// { -// if (handshakeReceived) -// { -// payload = m; -// callback(nullptr); -// return; -// } -// handshakeReceived = true; -// callback(nullptr); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// hub_connection->start().get(); -// -// hub_connection->send("method", json::value::array()).get(); -// -// ASSERT_EQ("{\"arguments\":[],\"target\":\"method\",\"type\":1}\x1e", payload); -//} -// -//TEST(send, does_not_wait_for_server_response) -//{ -// int call_number = -1; -// pplx::task_completion_event waitForSend; -// -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [waitForSend, call_number](std::function callback) mutable -// { -// std::string responses[] -// { -// "{ }\x1e", -// "{}" -// }; -// -// call_number = std::min(call_number + 1, 1); -// -// if (call_number == 1) -// { -// pplx::task(waitForSend).get(); -// } -// -// callback(responses[call_number], nullptr); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// hub_connection->start().get(); -// -// // wont block waiting for server response -// hub_connection->send("method", json::value::array()).get(); -// waitForSend.set(); -//} -// -//TEST(invoke, creates_correct_payload) -//{ -// std::string payload; -// bool handshakeReceived = false; -// -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, -// /* send function */[&payload, &handshakeReceived](const std::string& m, std::function callback) -// { -// if (handshakeReceived) -// { -// payload = m; -// callback(std::make_exception_ptr(std::runtime_error("error"))); -// return; -// } -// handshakeReceived = true; -// callback(nullptr); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// hub_connection->start().get(); -// -// try -// { -// hub_connection->invoke("method", json::value::array()).get(); -// } -// catch (...) -// { -// // the invoke is not setup to succeed because it's not needed in this test -// } -// -// ASSERT_EQ("{\"arguments\":[],\"invocationId\":\"0\",\"target\":\"method\",\"type\":1}\x1e", payload); -//} -// -//TEST(invoke, callback_not_called_if_send_throws) -//{ -// bool handshakeReceived = false; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, -// /* send function */[handshakeReceived](const std::string&, std::function callback) mutable -// { -// if (handshakeReceived) -// { -// callback(std::make_exception_ptr(std::runtime_error("error"))); -// return; -// } -// handshakeReceived = true; -// callback(nullptr); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// hub_connection->start().get(); -// -// try -// { -// hub_connection->invoke("method", json::value::array()).get(); -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const std::runtime_error& e) -// { -// ASSERT_STREQ("error", e.what()); -// } -// -// // stop completes all outstanding callbacks so if we did not remove a callback when `invoke_void` failed an -// // unobserved exception exception would be thrown. Note that this would happen on a different thread and would -// // crash the process -// hub_connection->stop().get(); -//} -// -//TEST(invoke, invoke_returns_value_returned_from_the_server) -//{ -// auto callback_registered_event = std::make_shared(); -// -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number, callback_registered_event](std::function callback) -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{ \"type\": 3, \"invocationId\": \"0\", \"result\": \"abc\" }\x1e" -// }; -// -// call_number = std::min(call_number + 1, 1); -// -// if (call_number > 0) -// { -// callback_registered_event->wait(); -// } -// -// callback(responses[call_number], nullptr); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// auto result = hub_connection->start() -// .then([hub_connection, callback_registered_event]() -// { -// auto t = hub_connection->invoke("method", json::value::array()); -// callback_registered_event->set(); -// return t; -// }).get(); -// -// ASSERT_EQ(_XPLATSTR("\"abc\""), result.serialize()); -//} -// -//TEST(invoke, invoke_propagates_errors_from_server_as_hub_exceptions) -//{ -// auto callback_registered_event = std::make_shared(); -// -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number, callback_registered_event](std::function callback) -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" -// }; -// -// call_number = std::min(call_number + 1, 1); -// -// if (call_number > 0) -// { -// callback_registered_event->wait(); -// } -// -// callback(responses[call_number], nullptr); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// try -// { -// hub_connection->start() -// .then([hub_connection, callback_registered_event]() -// { -// auto t = hub_connection->invoke("method", json::value::array()); -// callback_registered_event->set(); -// return t; -// }).get(); -// -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const hub_exception& e) -// { -// ASSERT_STREQ("\"Ooops\"", e.what()); -// } -//} -// -//TEST(invoke, unblocks_task_when_server_completes_call) -//{ -// auto callback_registered_event = std::make_shared(); -// -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number, callback_registered_event](std::function callback) -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{ \"type\": 3, \"invocationId\": \"0\" }\x1e" -// }; -// -// call_number = std::min(call_number + 1, 1); -// -// if (call_number > 0) -// { -// callback_registered_event->wait(); -// } -// -// callback(responses[call_number], nullptr); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// hub_connection->start() -// .then([hub_connection, callback_registered_event]() -// { -// auto t = hub_connection->invoke("method", json::value::array()); -// callback_registered_event->set(); -// return t; -// }).get(); -// -// // should not block -// ASSERT_TRUE(true); -//} -// -//TEST(receive, logs_if_callback_for_given_id_not_found) -//{ -// auto message_received_event = std::make_shared(); -// auto handshake_sent = std::make_shared(); -// -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number, message_received_event, handshake_sent](std::function callback) -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{ \"type\": 3, \"invocationId\": \"0\" }\x1e", -// "{}" -// }; -// -// handshake_sent->wait(1000); -// -// call_number = std::min(call_number + 1, 2); -// -// if (call_number > 1) -// { -// message_received_event->set(); -// } -// -// callback(responses[call_number], nullptr); -// }, -// [handshake_sent](const std::string&, std::function callback) -// { -// handshake_sent->set(); -// callback(nullptr); -// }); -// -// std::shared_ptr writer(std::make_shared()); -// auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::info); -// hub_connection->start().get(); -// -// ASSERT_FALSE(message_received_event->wait(5000)); -// -// auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); -// ASSERT_TRUE(log_entries.size() > 1); -// -// auto entry = remove_date_from_log_entry(log_entries[2]); -// ASSERT_EQ("[info ] no callback found for id: 0\n", entry) << dump_vector(log_entries); -//} -// -//TEST(invoke_void, invoke_creates_runtime_error) -//{ -// auto callback_registered_event = std::make_shared(); -// -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number, callback_registered_event](std::function callback) -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" -// }; -// -// call_number = std::min(call_number + 1, 1); -// -// if (call_number > 0) -// { -// callback_registered_event->wait(); -// } -// -// callback(responses[call_number], nullptr); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// try -// { -// hub_connection->start() -// .then([hub_connection, callback_registered_event]() -// { -// auto t = hub_connection->invoke("method", json::value::array()); -// callback_registered_event->set(); -// return t; -// }).get(); -// -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const hub_exception & e) -// { -// ASSERT_STREQ("\"Ooops\"", e.what()); -// ASSERT_FALSE(callback_registered_event->wait(0)); -// } -//} -// -//TEST(connection_id, can_get_connection_id) -//{ -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// ASSERT_EQ("", hub_connection->get_connection_id()); -// -// hub_connection->start().get(); -// auto connection_id = hub_connection->get_connection_id(); -// hub_connection->stop().get(); -// -// ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", connection_id); -// ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", hub_connection->get_connection_id()); -//} -// -//TEST(on, event_name_must_not_be_empty_string) -//{ -// auto hub_connection = create_hub_connection(); -// try -// { -// hub_connection->on("", [](const json::value&) {}); -// -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const std::invalid_argument& e) -// { -// ASSERT_STREQ("event_name cannot be empty", e.what()); -// } -//} -// -//TEST(on, cannot_register_multiple_handlers_for_event) -//{ -// auto hub_connection = create_hub_connection(); -// hub_connection->on("ping", [](const json::value&) {}); -// -// try -// { -// hub_connection->on("ping", [](const json::value&) {}); -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const signalr_exception& e) -// { -// ASSERT_STREQ("an action for this event has already been registered. event name: ping", e.what()); -// } -//} -// -//TEST(on, cannot_register_handler_if_connection_not_in_disconnected_state) -//{ -// try -// { -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// hub_connection->start().get(); -// -// hub_connection->on("myfunc", [](const web::json::value&) {}); -// -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const signalr_exception& e) -// { -// ASSERT_STREQ("can't register a handler if the connection is in a disconnected state", e.what()); -// } -//} -// -//TEST(invoke, invoke_throws_when_the_underlying_connection_is_not_valid) -//{ -// auto hub_connection = create_hub_connection(); -// -// try -// { -// hub_connection->invoke("method", json::value::array()).get(); -// ASSERT_TRUE(true); // exception expected but not thrown -// } -// catch (const signalr_exception& e) -// { -// ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); -// } -//} -// -//TEST(invoke, send_throws_when_the_underlying_connection_is_not_valid) -//{ -// auto hub_connection = create_hub_connection(); -// -// try -// { -// hub_connection->invoke("method", json::value::array()).get(); -// ASSERT_TRUE(true); // exception expected but not thrown -// } -// catch (const signalr_exception& e) -// { -// ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); -// } -//} +#include "test_utils.h" +#include "test_transport_factory.h" +#include "test_http_client.h" +#include "hub_connection_impl.h" +#include "trace_log_writer.h" +#include "memory_log_writer.h" +#include "signalrclient/hub_exception.h" +#include "signalrclient/signalr_exception.h" + +using namespace signalr; + +std::shared_ptr create_hub_connection(std::shared_ptr websocket_client = create_test_websocket_client(), + std::shared_ptr log_writer = std::make_shared(), trace_level trace_level = trace_level::all) +{ + return hub_connection_impl::create(create_uri(), trace_level, log_writer, + create_test_http_client(), std::make_unique(websocket_client)); +} + +TEST(url, negotiate_appended_to_url) +{ + std::string base_urls[] = { "http://fakeuri", "http://fakeuri/" }; + + for (const auto& base_url : base_urls) + { + std::string requested_url; + auto http_client = std::make_unique([&requested_url](const std::string& url, http_request) + { + requested_url = url; + return http_response{ 404, "" }; + }); + + auto hub_connection = hub_connection_impl::create(base_url, trace_level::none, + std::make_shared(), std::move(http_client), + std::make_unique(create_test_websocket_client())); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + try + { + mre.get(); + ASSERT_TRUE(false); + } + catch (const std::exception&) {} + + ASSERT_EQ("http://fakeuri/negotiate", requested_url); + } +} + +TEST(start, start_starts_connection) +{ + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); + auto hub_connection = create_hub_connection(websocket_client); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); +} + +TEST(start, start_sends_handshake) +{ + auto message = std::make_shared(); + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, + /* send function */ [message](const std::string& msg, std::function callback) { *message = msg; callback(nullptr); }); + auto hub_connection = create_hub_connection(websocket_client); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + ASSERT_EQ("{\"protocol\":\"json\",\"version\":1}\x1e", *message); + + ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); +} + +TEST(start, start_waits_for_handshake_response) +{ + pplx::task_completion_event tce; + pplx::task_completion_event tceWaitForSend; + auto websocket_client = create_test_websocket_client( + /* receive function */ [tce, tceWaitForSend](std::function callback) + { + tceWaitForSend.set(); + pplx::task(tce).get(); + callback("{ }\x1e", nullptr); + }); + auto hub_connection = create_hub_connection(websocket_client); + + auto mre = manual_reset_event(); + auto done = false; + hub_connection->start([&mre, &done](std::exception_ptr exception) + { + done = true; + mre.set(exception); + }); + + pplx::task(tceWaitForSend).get(); + ASSERT_FALSE(done); + tce.set(); + mre.get(); + + ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); +} + +TEST(start, start_fails_for_handshake_response_with_error) +{ + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{\"error\":\"bad things\"}\x1e", nullptr); }); + auto hub_connection = create_hub_connection(websocket_client); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + try + { + mre.get(); + ASSERT_TRUE(false); + } + catch (const std::exception& ex) + { + ASSERT_STREQ("Received an error during handshake: bad things", ex.what()); + } + + ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); +} + +TEST(start, start_fails_if_stop_called_before_handshake_response) +{ + pplx::task_completion_event tce; + pplx::task_completion_event tceWaitForSend; + auto websocket_client = create_test_websocket_client( + /* receive function */ [tce](std::function callback) + { + auto str = pplx::task(tce).get(); + callback(str, nullptr); + }, + /* send function */ [tceWaitForSend](const std::string &, std::function callback) + { + tceWaitForSend.set(); + callback(nullptr); + }); + auto hub_connection = create_hub_connection(websocket_client); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + pplx::task(tceWaitForSend).get(); + hub_connection->stop([](std::exception_ptr) {}); + + try + { + mre.get(); + ASSERT_TRUE(false); + } + catch (std::exception ex) + { + ASSERT_STREQ("connection closed while handshake was in progress.", ex.what()); + } + + ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); +} + +TEST(stop, stop_stops_connection) +{ + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); + auto hub_connection = create_hub_connection(websocket_client); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + hub_connection->stop([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); +} + +TEST(stop, disconnected_callback_called_when_hub_connection_stops) +{ + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); + auto hub_connection = create_hub_connection(websocket_client); + + auto disconnected_invoked = false; + hub_connection->set_disconnected([&disconnected_invoked]() { disconnected_invoked = true; }); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + hub_connection->stop([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + ASSERT_TRUE(disconnected_invoked); +} + +TEST(stop, connection_stopped_when_going_out_of_scope) +{ + std::shared_ptr writer(std::make_shared()); + + { + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); + auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::state_changes); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + } + + auto memory_writer = std::dynamic_pointer_cast(writer); + + // The underlying connection_impl will be destroyed when the last reference to shared_ptr holding is released. This can happen + // on a different thread in which case the dtor will be invoked on a different thread so we need to wait for this + // to happen and if it does not the test will fail. There is nothing we can block on. + for (int wait_time_ms = 5; wait_time_ms < 100 && memory_writer->get_log_entries().size() < 4; wait_time_ms <<= 1) + { + std::this_thread::sleep_for(std::chrono::milliseconds(wait_time_ms)); + } + + auto log_entries = memory_writer->get_log_entries(); + ASSERT_EQ(4U, log_entries.size()) << dump_vector(log_entries); + ASSERT_EQ("[state change] disconnected -> connecting\n", remove_date_from_log_entry(log_entries[0])); + ASSERT_EQ("[state change] connecting -> connected\n", remove_date_from_log_entry(log_entries[1])); + ASSERT_EQ("[state change] connected -> disconnecting\n", remove_date_from_log_entry(log_entries[2])); + ASSERT_EQ("[state change] disconnecting -> disconnected\n", remove_date_from_log_entry(log_entries[3])); +} + +TEST(stop, stop_cancels_pending_callbacks) +{ + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{}" + }; + + if (call_number < 1) + { + call_number++; + } + + callback(responses[call_number], nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + auto invoke_mre = manual_reset_event(); + hub_connection->invoke("method", json::value::array(), [&invoke_mre](const json::value&, std::exception_ptr exception) + { + invoke_mre.set(exception); + }); + + hub_connection->stop([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + try + { + invoke_mre.get(); + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const signalr_exception& e) + { + ASSERT_STREQ("\"connection was stopped before invocation result was received\"", e.what()); + } +} + +TEST(stop, pending_callbacks_finished_if_hub_connections_goes_out_of_scope) +{ + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{}" + }; + + if (call_number < 1) + { + call_number++; + } + + callback(responses[call_number], nullptr); + }); + + auto invoke_mre = manual_reset_event(); + + { + auto hub_connection = create_hub_connection(websocket_client); + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + hub_connection->invoke("method", json::value::array(), [&invoke_mre](const json::value&, std::exception_ptr exception) + { + invoke_mre.set(exception); + }); + } + + try + { + invoke_mre.get(); + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const signalr_exception& e) + { + ASSERT_STREQ("\"connection went out of scope before invocation result was received\"", e.what()); + } +} + +TEST(hub_invocation, hub_connection_invokes_users_code_on_hub_invocations) +{ + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{ \"type\": 1, \"target\": \"BROADcast\", \"arguments\": [ \"message\", 1 ] }\x1e" + }; + + call_number = std::min(call_number + 1, 1); + + callback(responses[call_number], nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + + auto payload = std::make_shared(); + auto on_broadcast_event = std::make_shared(); + hub_connection->on("broadCAST", [on_broadcast_event, payload](const json::value& message) + { + *payload = utility::conversions::to_utf8string(message.serialize()); + on_broadcast_event->set(); + }); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + ASSERT_FALSE(on_broadcast_event->wait(5000)); + + ASSERT_EQ("[\"message\",1]", *payload); +} + +TEST(send, creates_correct_payload) +{ + std::string payload; + bool handshakeReceived = false; + + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, + /* send function */[&payload, &handshakeReceived](const std::string& m, std::function callback) + { + if (handshakeReceived) + { + payload = m; + callback(nullptr); + return; + } + handshakeReceived = true; + callback(nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + hub_connection->send("method", json::value::array(), [&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + ASSERT_EQ("{\"arguments\":[],\"target\":\"method\",\"type\":1}\x1e", payload); +} + +TEST(send, does_not_wait_for_server_response) +{ + int call_number = -1; + pplx::task_completion_event waitForSend; + + auto websocket_client = create_test_websocket_client( + /* receive function */ [waitForSend, call_number](std::function callback) mutable + { + std::string responses[] + { + "{ }\x1e", + "{}" + }; + + call_number = std::min(call_number + 1, 1); + + if (call_number == 1) + { + pplx::task(waitForSend).get(); + } + + callback(responses[call_number], nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + // wont block waiting for server response + hub_connection->send("method", json::value::array(), [&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + mre.get(); + waitForSend.set(); +} + +TEST(invoke, creates_correct_payload) +{ + std::string payload; + bool handshakeReceived = false; + + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, + /* send function */[&payload, &handshakeReceived](const std::string& m, std::function callback) + { + if (handshakeReceived) + { + payload = m; + callback(std::make_exception_ptr(std::runtime_error("error"))); + return; + } + handshakeReceived = true; + callback(nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + hub_connection->invoke("method", json::value::array(), [&mre](const json::value&, std::exception_ptr exception) + { + mre.set(exception); + }); + + try + { + mre.get(); + ASSERT_TRUE(false); + } + catch (...) + { + // the invoke is not setup to succeed because it's not needed in this test + } + + ASSERT_EQ("{\"arguments\":[],\"invocationId\":\"0\",\"target\":\"method\",\"type\":1}\x1e", payload); +} + +TEST(invoke, callback_not_called_if_send_throws) +{ + bool handshakeReceived = false; + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, + /* send function */[handshakeReceived](const std::string&, std::function callback) mutable + { + if (handshakeReceived) + { + callback(std::make_exception_ptr(std::runtime_error("error"))); + return; + } + handshakeReceived = true; + callback(nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + hub_connection->invoke("method", json::value::array(), [&mre](const json::value&, std::exception_ptr exception) + { + mre.set(exception); + }); + + try + { + mre.get(); + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const std::runtime_error& e) + { + ASSERT_STREQ("error", e.what()); + } + + // stop completes all outstanding callbacks so if we did not remove a callback when `invoke_void` failed an + // unobserved exception exception would be thrown. Note that this would happen on a different thread and would + // crash the process + hub_connection->stop([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); +} + +TEST(invoke, invoke_returns_value_returned_from_the_server) +{ + auto callback_registered_event = std::make_shared(); + + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number, callback_registered_event](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{ \"type\": 3, \"invocationId\": \"0\", \"result\": \"abc\" }\x1e" + }; + + call_number = std::min(call_number + 1, 1); + + if (call_number > 0) + { + callback_registered_event->wait(); + } + + callback(responses[call_number], nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + auto invoke_mre = manual_reset_event(); + hub_connection->invoke("method", json::value::array(), [&invoke_mre](const json::value& message, std::exception_ptr exception) + { + if (exception) + { + invoke_mre.set(exception); + } + else + { + invoke_mre.set(message); + } + }); + + callback_registered_event->set(); + + auto result = invoke_mre.get(); + + ASSERT_EQ(_XPLATSTR("\"abc\""), result.serialize()); +} + +TEST(invoke, invoke_propagates_errors_from_server_as_hub_exceptions) +{ + auto callback_registered_event = std::make_shared(); + + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number, callback_registered_event](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" + }; + + call_number = std::min(call_number + 1, 1); + + if (call_number > 0) + { + callback_registered_event->wait(); + } + + callback(responses[call_number], nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + hub_connection->invoke("method", json::value::array(), [&mre](const json::value&, std::exception_ptr exception) + { + mre.set(exception); + }); + + callback_registered_event->set(); + + try + { + mre.get(); + + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const hub_exception& e) + { + ASSERT_STREQ("\"Ooops\"", e.what()); + } +} + +TEST(invoke, unblocks_task_when_server_completes_call) +{ + auto callback_registered_event = std::make_shared(); + + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number, callback_registered_event](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{ \"type\": 3, \"invocationId\": \"0\" }\x1e" + }; + + call_number = std::min(call_number + 1, 1); + + if (call_number > 0) + { + callback_registered_event->wait(); + } + + callback(responses[call_number], nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + hub_connection->invoke("method", json::value::array(), [&mre](const json::value&, std::exception_ptr exception) + { + mre.set(exception); + }); + + callback_registered_event->set(); + + mre.get(); + + // should not block + ASSERT_TRUE(true); +} + +TEST(receive, logs_if_callback_for_given_id_not_found) +{ + auto message_received_event = std::make_shared(); + auto handshake_sent = std::make_shared(); + + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number, message_received_event, handshake_sent](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{ \"type\": 3, \"invocationId\": \"0\" }\x1e", + "{}" + }; + + handshake_sent->wait(1000); + + call_number = std::min(call_number + 1, 2); + + if (call_number > 1) + { + message_received_event->set(); + } + + callback(responses[call_number], nullptr); + }, + [handshake_sent](const std::string&, std::function callback) + { + handshake_sent->set(); + callback(nullptr); + }); + + std::shared_ptr writer(std::make_shared()); + auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::info); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + ASSERT_FALSE(message_received_event->wait(5000)); + + auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); + ASSERT_TRUE(log_entries.size() > 1); + + auto entry = remove_date_from_log_entry(log_entries[2]); + ASSERT_EQ("[info ] no callback found for id: 0\n", entry) << dump_vector(log_entries); +} + +TEST(invoke_void, invoke_creates_runtime_error) +{ + auto callback_registered_event = std::make_shared(); + + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number, callback_registered_event](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" + }; + + call_number = std::min(call_number + 1, 1); + + if (call_number > 0) + { + callback_registered_event->wait(); + } + + callback(responses[call_number], nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + hub_connection->invoke("method", json::value::array(), [&mre](const json::value&, std::exception_ptr exception) + { + mre.set(exception); + }); + + callback_registered_event->set(); + + try + { + mre.get(); + + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const hub_exception & e) + { + ASSERT_STREQ("\"Ooops\"", e.what()); + ASSERT_FALSE(callback_registered_event->wait(0)); + } +} + +TEST(connection_id, can_get_connection_id) +{ + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); + auto hub_connection = create_hub_connection(websocket_client); + + ASSERT_EQ("", hub_connection->get_connection_id()); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + auto connection_id = hub_connection->get_connection_id(); + + hub_connection->stop([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", connection_id); + ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", hub_connection->get_connection_id()); +} + +TEST(on, event_name_must_not_be_empty_string) +{ + auto hub_connection = create_hub_connection(); + try + { + hub_connection->on("", [](const json::value&) {}); + + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const std::invalid_argument& e) + { + ASSERT_STREQ("event_name cannot be empty", e.what()); + } +} + +TEST(on, cannot_register_multiple_handlers_for_event) +{ + auto hub_connection = create_hub_connection(); + hub_connection->on("ping", [](const json::value&) {}); + + try + { + hub_connection->on("ping", [](const json::value&) {}); + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const signalr_exception& e) + { + ASSERT_STREQ("an action for this event has already been registered. event name: ping", e.what()); + } +} + +TEST(on, cannot_register_handler_if_connection_not_in_disconnected_state) +{ + try + { + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); + auto hub_connection = create_hub_connection(websocket_client); + + auto mre = manual_reset_event(); + hub_connection->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + hub_connection->on("myfunc", [](const web::json::value&) {}); + + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const signalr_exception& e) + { + ASSERT_STREQ("can't register a handler if the connection is in a disconnected state", e.what()); + } +} + +TEST(invoke, invoke_throws_when_the_underlying_connection_is_not_valid) +{ + auto hub_connection = create_hub_connection(); + + auto mre = manual_reset_event(); + hub_connection->invoke("method", json::value::array(), [&mre](const json::value&, std::exception_ptr exception) + { + mre.set(exception); + }); + + try + { + mre.get(); + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const signalr_exception& e) + { + ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); + } +} + +TEST(invoke, send_throws_when_the_underlying_connection_is_not_valid) +{ + auto hub_connection = create_hub_connection(); + + auto mre = manual_reset_event(); + hub_connection->invoke("method", json::value::array(), [&mre](const json::value&, std::exception_ptr exception) + { + mre.set(exception); + }); + + try + { + mre.get(); + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const signalr_exception& e) + { + ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); + } +} From a9d309db9a43f2b4059399dae47e36eba4fa8e9d Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Thu, 21 Mar 2019 13:22:59 -0700 Subject: [PATCH 4/9] cleanup --- .../cpp/include/signalrclient/connection.h | 6 +++--- .../cpp/include/signalrclient/hub_connection.h | 8 ++++---- .../clients/cpp/src/signalrclient/connection.cpp | 6 +++--- .../cpp/src/signalrclient/connection_impl.cpp | 6 +++--- .../cpp/src/signalrclient/connection_impl.h | 6 +++--- .../cpp/src/signalrclient/hub_connection.cpp | 14 ++++++++------ .../cpp/src/signalrclient/hub_connection_impl.cpp | 15 ++++++++------- .../cpp/src/signalrclient/hub_connection_impl.h | 10 +++++----- .../clients/cpp/src/signalrclient/transport.h | 6 +++--- .../cpp/src/signalrclient/websocket_transport.cpp | 13 ++++++++----- .../cpp/src/signalrclient/websocket_transport.h | 6 +++--- 11 files changed, 51 insertions(+), 45 deletions(-) diff --git a/src/SignalR/clients/cpp/include/signalrclient/connection.h b/src/SignalR/clients/cpp/include/signalrclient/connection.h index fe646a6f5f26..b3af11d09f79 100644 --- a/src/SignalR/clients/cpp/include/signalrclient/connection.h +++ b/src/SignalR/clients/cpp/include/signalrclient/connection.h @@ -28,16 +28,16 @@ namespace signalr connection& operator=(const connection&) = delete; - SIGNALRCLIENT_API void __cdecl start(std::function callback); + SIGNALRCLIENT_API void __cdecl start(std::function callback) noexcept; - SIGNALRCLIENT_API void __cdecl send(const std::string& data, std::function callback); + SIGNALRCLIENT_API void __cdecl send(const std::string& data, std::function callback) noexcept; SIGNALRCLIENT_API void __cdecl set_message_received(const message_received_handler& message_received_callback); SIGNALRCLIENT_API void __cdecl set_disconnected(const std::function& disconnected_callback); SIGNALRCLIENT_API void __cdecl set_client_config(const signalr_client_config& config); - SIGNALRCLIENT_API void __cdecl stop(std::function callback); + SIGNALRCLIENT_API void __cdecl stop(std::function callback) noexcept; SIGNALRCLIENT_API connection_state __cdecl get_connection_state() const noexcept; SIGNALRCLIENT_API std::string __cdecl get_connection_id() const; diff --git a/src/SignalR/clients/cpp/include/signalrclient/hub_connection.h b/src/SignalR/clients/cpp/include/signalrclient/hub_connection.h index 35991acefe2f..68597e5420b8 100644 --- a/src/SignalR/clients/cpp/include/signalrclient/hub_connection.h +++ b/src/SignalR/clients/cpp/include/signalrclient/hub_connection.h @@ -30,8 +30,8 @@ namespace signalr hub_connection& operator=(const hub_connection&) = delete; - SIGNALRCLIENT_API void __cdecl start(std::function callback); - SIGNALRCLIENT_API void __cdecl stop(std::function callback); + SIGNALRCLIENT_API void __cdecl start(std::function callback) noexcept; + SIGNALRCLIENT_API void __cdecl stop(std::function callback) noexcept; SIGNALRCLIENT_API connection_state __cdecl get_connection_state() const; SIGNALRCLIENT_API std::string __cdecl get_connection_id() const; @@ -42,9 +42,9 @@ namespace signalr SIGNALRCLIENT_API void __cdecl on(const std::string& event_name, const method_invoked_handler& handler); - SIGNALRCLIENT_API void invoke(const std::string& method_name, const web::json::value& arguments = web::json::value::array(), std::function callback = [](const web::json::value&, std::exception_ptr) {}); + SIGNALRCLIENT_API void invoke(const std::string& method_name, const web::json::value& arguments = web::json::value::array(), std::function callback = [](const web::json::value&, std::exception_ptr) {}) noexcept; - SIGNALRCLIENT_API void send(const std::string& method_name, const web::json::value& arguments = web::json::value::array(), std::function callback = [](std::exception_ptr) {}); + SIGNALRCLIENT_API void send(const std::string& method_name, const web::json::value& arguments = web::json::value::array(), std::function callback = [](std::exception_ptr) {}) noexcept; private: std::shared_ptr m_pImpl; diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection.cpp b/src/SignalR/clients/cpp/src/signalrclient/connection.cpp index 4d5ad220fe64..6a5a94064030 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/connection.cpp @@ -16,12 +16,12 @@ namespace signalr // undefinded behavior since we are using an incomplete type. More details here: http://herbsutter.com/gotw/_100/ connection::~connection() = default; - void connection::start(std::function callback) + void connection::start(std::function callback) noexcept { m_pImpl->start(callback); } - void connection::send(const std::string& data, std::function callback) + void connection::send(const std::string& data, std::function callback) noexcept { m_pImpl->send(data, callback); } @@ -41,7 +41,7 @@ namespace signalr m_pImpl->set_client_config(config); } - void connection::stop(std::function callback) + void connection::stop(std::function callback) noexcept { m_pImpl->stop(callback); } diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp index 0b72fa07dfb5..4348db656278 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp @@ -74,7 +74,7 @@ namespace signalr change_state(connection_state::disconnected); } - void connection_impl::start(std::function callback) + void connection_impl::start(std::function callback) noexcept { { std::lock_guard lock(m_stop_lock); @@ -374,7 +374,7 @@ namespace signalr } } - void connection_impl::send(const std::string& data, std::function callback) + void connection_impl::send(const std::string& data, std::function callback) noexcept { // To prevent an (unlikely) condition where the transport is nulled out after we checked the connection_state // and before sending data we store the pointer in the local variable. In this case `send()` will throw but @@ -416,7 +416,7 @@ namespace signalr }); } - void connection_impl::stop(std::function callback) + void connection_impl::stop(std::function callback) noexcept { m_logger.log(trace_level::info, "stopping connection"); diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h index 3862341b86fc..b347fed9885b 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h @@ -36,9 +36,9 @@ namespace signalr ~connection_impl(); - void start(std::function callback); - void send(const std::string &data, std::function callback); - void stop(std::function callback); + void start(std::function callback) noexcept; + void send(const std::string &data, std::function callback) noexcept; + void stop(std::function callback) noexcept; connection_state get_connection_state() const noexcept; std::string get_connection_id() const noexcept; diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp b/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp index d841f8663160..9d105c81a61e 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp @@ -17,12 +17,12 @@ namespace signalr // undefinded behavior since we are using an incomplete type. More details here: http://herbsutter.com/gotw/_100/ hub_connection::~hub_connection() = default; - void hub_connection::start(std::function callback) + void hub_connection::start(std::function callback) noexcept { m_pImpl->start(callback); } - void hub_connection::stop(std::function callback) + void hub_connection::stop(std::function callback) noexcept { m_pImpl->stop(callback); } @@ -37,21 +37,23 @@ namespace signalr return m_pImpl->on(event_name, handler); } - void hub_connection::invoke(const std::string& method_name, const web::json::value& arguments, std::function callback) + void hub_connection::invoke(const std::string& method_name, const web::json::value& arguments, std::function callback) noexcept { if (!m_pImpl) { - throw signalr_exception("invoke() cannot be called on uninitialized hub_connection instance"); + callback(web::json::value(), std::make_exception_ptr(signalr_exception("invoke() cannot be called on uninitialized hub_connection instance"))); + return; } return m_pImpl->invoke(method_name, arguments, callback); } - void hub_connection::send(const std::string& method_name, const web::json::value& arguments, std::function callback) + void hub_connection::send(const std::string& method_name, const web::json::value& arguments, std::function callback) noexcept { if (!m_pImpl) { - throw signalr_exception("send() cannot be called on uninitialized hub_connection instance"); + callback(std::make_exception_ptr(signalr_exception("send() cannot be called on uninitialized hub_connection instance"))); + return; } m_pImpl->send(method_name, arguments, callback); diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp index 74c8147f13dc..577b1a260523 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp @@ -96,12 +96,13 @@ namespace signalr m_subscriptions.insert(std::pair> {event_name, handler}); } - void hub_connection_impl::start(std::function callback) + void hub_connection_impl::start(std::function callback) noexcept { if (m_connection->get_connection_state() != connection_state::disconnected) { - throw signalr_exception( - "the connection can only be started if it is in the disconnected state"); + callback(std::make_exception_ptr(signalr_exception( + "the connection can only be started if it is in the disconnected state"))); + return; } m_connection->set_client_config(m_signalr_client_config); @@ -177,7 +178,7 @@ namespace signalr }); } - void hub_connection_impl::stop(std::function callback) + void hub_connection_impl::stop(std::function callback) noexcept { m_callback_manager.clear(json::value::parse(_XPLATSTR("{ \"error\" : \"connection was stopped before invocation result was received\"}"))); m_connection->stop(callback); @@ -299,7 +300,7 @@ namespace signalr return true; } - void hub_connection_impl::invoke(const std::string& method_name, const json::value& arguments, std::function callback) + void hub_connection_impl::invoke(const std::string& method_name, const json::value& arguments, std::function callback) noexcept { _ASSERTE(arguments.is_array()); @@ -311,7 +312,7 @@ namespace signalr [callback](const std::exception_ptr e){ callback(json::value(), e); }); } - void hub_connection_impl::send(const std::string& method_name, const json::value& arguments, std::function callback) + void hub_connection_impl::send(const std::string& method_name, const json::value& arguments, std::function callback) noexcept { _ASSERTE(arguments.is_array()); @@ -321,7 +322,7 @@ namespace signalr } void hub_connection_impl::invoke_hub_method(const std::string& method_name, const json::value& arguments, - const std::string& callback_id, std::function set_completion, std::function set_exception) + const std::string& callback_id, std::function set_completion, std::function set_exception) noexcept { json::value request; request[_XPLATSTR("type")] = json::value(1); diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h index a265d048a6dd..44c42b4614ac 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h @@ -32,11 +32,11 @@ namespace signalr void on(const std::string& event_name, const std::function& handler); - void invoke(const std::string& method_name, const json::value& arguments, std::function callback); - void send(const std::string& method_name, const json::value& arguments, std::function callback); + void invoke(const std::string& method_name, const json::value& arguments, std::function callback) noexcept; + void send(const std::string& method_name, const json::value& arguments, std::function callback) noexcept; - void start(std::function callback); - void stop(std::function callback); + void start(std::function callback) noexcept; + void stop(std::function callback) noexcept; connection_state get_connection_state() const noexcept; std::string get_connection_id() const; @@ -62,7 +62,7 @@ namespace signalr void process_message(const std::string& message); void invoke_hub_method(const std::string& method_name, const json::value& arguments, const std::string& callback_id, - std::function set_completion, std::function set_exception); + std::function set_completion, std::function set_exception) noexcept; bool invoke_callback(const web::json::value& message); }; } diff --git a/src/SignalR/clients/cpp/src/signalrclient/transport.h b/src/SignalR/clients/cpp/src/signalrclient/transport.h index 4bb3a5643df4..cde0ed1e8e2f 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/transport.h +++ b/src/SignalR/clients/cpp/src/signalrclient/transport.h @@ -17,11 +17,11 @@ namespace signalr virtual ~transport(); - virtual void start(const std::string& url, transfer_format format, std::function callback) = 0; - virtual void stop(std::function callback) = 0; + virtual void start(const std::string& url, transfer_format format, std::function callback) noexcept = 0; + virtual void stop(std::function callback) noexcept = 0; virtual void on_close(std::function callback) = 0; - virtual void send(std::string payload, std::function callback) = 0; + virtual void send(std::string payload, std::function callback) noexcept = 0; virtual void on_receive(std::function callback) = 0; diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp index 85e4f556eecd..ad56e8b6400f 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp @@ -142,17 +142,20 @@ namespace signalr } } - void websocket_transport::start(const std::string& url, transfer_format format, std::function callback) + void websocket_transport::start(const std::string& url, transfer_format format, std::function callback) noexcept { web::uri uri(utility::conversions::to_string_t(url)); _ASSERTE(uri.scheme() == _XPLATSTR("ws") || uri.scheme() == _XPLATSTR("wss")); { - std::lock_guard stop_lock(m_start_stop_lock); + std::unique_lock stop_lock(m_start_stop_lock); if (!m_receive_loop_cts.get_token().is_canceled()) { - throw signalr_exception("transport already connected"); + // just in case the user tries to start again in the callback on error + stop_lock.unlock(); + callback(std::make_exception_ptr(signalr_exception("transport already connected"))); + return; } m_logger.log(trace_level::info, @@ -197,7 +200,7 @@ namespace signalr } } - void websocket_transport::stop(std::function callback) + void websocket_transport::stop(std::function callback) noexcept { std::shared_ptr websocket_client = nullptr; @@ -252,7 +255,7 @@ namespace signalr m_process_response_callback = callback; } - void websocket_transport::send(std::string payload, std::function callback) + void websocket_transport::send(std::string payload, std::function callback) noexcept { safe_get_websocket_client()->send(payload, [callback](std::exception_ptr exception) { diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h index 8dc362c696c4..45ef6b41e1ea 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h @@ -26,11 +26,11 @@ namespace signalr transport_type get_transport_type() const noexcept override; - void start(const std::string& url, transfer_format format, std::function callback) override; - void stop(std::function callback) override; + void start(const std::string& url, transfer_format format, std::function callback) noexcept override; + void stop(std::function callback) noexcept override; void on_close(std::function callback) override; - void send(std::string payload, std::function callback) override; + void send(std::string payload, std::function callback) noexcept override; void on_receive(std::function) override; From 7a83cf7e2f5876cc6a1003406197580ce038b19d Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Thu, 21 Mar 2019 13:35:00 -0700 Subject: [PATCH 5/9] cleanup --- .../src/signalrclient/hub_connection_impl.cpp | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp index 577b1a260523..149151a92497 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp @@ -111,7 +111,6 @@ namespace signalr std::weak_ptr weak_connection = shared_from_this(); return m_connection->start([weak_connection, callback](std::exception_ptr start_exception) { - //startTask.get(); auto connection = weak_connection.lock(); if (!connection) { @@ -122,25 +121,15 @@ namespace signalr if (start_exception) { - connection->m_connection->stop([start_exception, callback, connection](std::exception_ptr exception) + connection->m_connection->stop([start_exception, callback, connection](std::exception_ptr) { try { pplx::task(connection->m_handshakeTask).get(); } catch (...) {} - if (start_exception) - { - callback(start_exception); - } - else if (exception) - { - callback(exception); - } - else - { - callback(nullptr); - } + + callback(start_exception); }); return; } From d4915e8a71646db9d03b91d747164e7bb049f108 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Fri, 22 Mar 2019 11:35:14 -0700 Subject: [PATCH 6/9] fix --- .../clients/cpp/src/signalrclient/hub_connection_impl.cpp | 2 +- .../clients/cpp/src/signalrclient/websocket_transport.cpp | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp index 149151a92497..9676ba997107 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp @@ -109,7 +109,7 @@ namespace signalr m_handshakeTask = pplx::task_completion_event(); m_handshakeReceived = false; std::weak_ptr weak_connection = shared_from_this(); - return m_connection->start([weak_connection, callback](std::exception_ptr start_exception) + m_connection->start([weak_connection, callback](std::exception_ptr start_exception) { auto connection = weak_connection.lock(); if (!connection) diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp index ad56e8b6400f..b00e677dcbe6 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp @@ -148,12 +148,10 @@ namespace signalr _ASSERTE(uri.scheme() == _XPLATSTR("ws") || uri.scheme() == _XPLATSTR("wss")); { - std::unique_lock stop_lock(m_start_stop_lock); + std::lock_guard stop_lock(m_start_stop_lock); if (!m_receive_loop_cts.get_token().is_canceled()) { - // just in case the user tries to start again in the callback on error - stop_lock.unlock(); callback(std::make_exception_ptr(signalr_exception("transport already connected"))); return; } From 62d985218086356ae7c26c455fa9224d90884636 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Mon, 25 Mar 2019 16:11:22 -0700 Subject: [PATCH 7/9] compile --- .../HubConnectionSample.cpp | 75 ++++-- .../Build/VS/signalrclient-e2e-tests.vcxproj | 4 +- .../signalrclient-e2e-tests.vcxproj.filters | 3 - .../connection_tests.cpp | 105 ++++++-- .../hub_connection_tests.cpp | 247 +++++++++++++----- .../cpp/test/signalrclienttests/test_utils.h | 2 - 6 files changed, 318 insertions(+), 118 deletions(-) diff --git a/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp b/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp index d1e8896986a4..d12de18c7b25 100644 --- a/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp +++ b/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp @@ -7,6 +7,7 @@ #include #include "hub_connection.h" #include "log_writer.h" +#include class logger : public signalr::log_writer { @@ -23,13 +24,16 @@ void send_message(signalr::hub_connection& connection, const std::string& messag args[0] = web::json::value(utility::conversions::to_string_t(message)); // if you get an internal compiler error uncomment the lambda below or install VS Update 4 - connection.invoke("Send", args) - .then([](pplx::task invoke_task) // fire and forget but we need to observe exceptions + connection.invoke("Send", args, [](const web::json::value& value, std::exception_ptr exception) { try { - auto val = invoke_task.get(); - ucout << U("Received: ") << val.serialize() << std::endl; + if (exception) + { + std::rethrow_exception(exception); + } + + ucout << U("Received: ") << value.serialize() << std::endl; } catch (const std::exception &e) { @@ -41,44 +45,63 @@ void send_message(signalr::hub_connection& connection, const std::string& messag void chat() { signalr::hub_connection connection("http://localhost:5000/default", signalr::trace_level::all, std::make_shared()); - connection.on("Send", [](const web::json::value& m) + connection.on("Send", [](const web::json::value & m) { ucout << std::endl << m.at(0).as_string() << /*U(" wrote:") << m.at(1).as_string() <<*/ std::endl << U("Enter your message: "); }); - connection.start() - .then([&connection]() + std::promise task; + connection.start([&connection, &task](std::exception_ptr exception) + { + if (exception) { - ucout << U("Enter your message:"); - for (;;) + try { - std::string message; - std::getline(std::cin, message); - - if (message == ":q") - { - break; - } - send_message(connection, message); } - }) - .then([&connection]() // fine to capture by reference - we are blocking so it is guaranteed to be valid + catch (const std::exception & ex) + { + ucout << U("exception when starting connection: ") << ex.what() << std::endl; + } + task.set_value(); + return; + } + + ucout << U("Enter your message:"); + for (;;) { - return connection.stop(); - }) - .then([](pplx::task stop_task) + std::string message; + std::getline(std::cin, message); + + if (message == ":q") + { + break; + } + + send_message(connection, message); + } + + connection.stop([&task](std::exception_ptr exception) { try { - stop_task.get(); + if (exception) + { + std::rethrow_exception(exception); + } + ucout << U("connection stopped successfully") << std::endl; } - catch (const std::exception &e) + catch (const std::exception & e) { - ucout << U("exception when starting or stopping connection: ") << e.what() << std::endl; + ucout << U("exception when stopping connection: ") << e.what() << std::endl; } - }).get(); + + task.set_value(); + }); + }); + + task.get_future().get(); } int main() diff --git a/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/Build/VS/signalrclient-e2e-tests.vcxproj b/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/Build/VS/signalrclient-e2e-tests.vcxproj index 0cb16d4a30aa..9a470efcf9c9 100644 --- a/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/Build/VS/signalrclient-e2e-tests.vcxproj +++ b/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/Build/VS/signalrclient-e2e-tests.vcxproj @@ -30,7 +30,7 @@ _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(GoogleTestSubmoduleRoot)googletest\include;..\..\..\..\include\signalrclient;%(AdditionalIncludeDirectories) + $(GoogleTestSubmoduleRoot)googletest\include;..\..\..\..\include;%(AdditionalIncludeDirectories) ProgramDatabase @@ -75,4 +75,4 @@ - + \ No newline at end of file diff --git a/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/Build/VS/signalrclient-e2e-tests.vcxproj.filters b/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/Build/VS/signalrclient-e2e-tests.vcxproj.filters index 80f167f1002a..d441a9f7ab1b 100644 --- a/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/Build/VS/signalrclient-e2e-tests.vcxproj.filters +++ b/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/Build/VS/signalrclient-e2e-tests.vcxproj.filters @@ -39,7 +39,4 @@ Source Files - - - \ No newline at end of file diff --git a/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/connection_tests.cpp b/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/connection_tests.cpp index 6e9fc8b99270..b089ca9fcf06 100644 --- a/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/connection_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/connection_tests.cpp @@ -7,8 +7,9 @@ #include #include "cpprest/details/basic_types.h" #include "cpprest/json.h" -#include "connection.h" -#include "hub_connection.h" +#include "signalrclient/connection.h" +#include "signalrclient/hub_connection.h" +#include "../signalrclienttests/test_utils.h" extern std::string url; @@ -16,13 +17,29 @@ TEST(connection_tests, connection_status_start_stop) { auto conn = std::make_shared(url + "raw-connection"); - conn->start().get(); + auto mre = manual_reset_event(); + conn->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); ASSERT_EQ(conn->get_connection_state(), signalr::connection_state::connected); - conn->stop().get(); + conn->stop([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); ASSERT_EQ(conn->get_connection_state(), signalr::connection_state::disconnected); - conn->start().get(); + conn->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); ASSERT_EQ(conn->get_connection_state(), signalr::connection_state::connected); } @@ -38,14 +55,23 @@ TEST(connection_tests, send_message) received_event->set(); }); - conn->start().then([conn]() + auto mre = manual_reset_event(); + conn->start([&mre](std::exception_ptr exception) { - web::json::value obj; - obj[U("type")] = web::json::value::number(0); - obj[U("value")] = web::json::value::string(U("test")); - return conn->send(utility::conversions::to_utf8string(obj.serialize())); + mre.set(exception); + }); + + mre.get(); + + web::json::value obj; + obj[U("type")] = web::json::value::number(0); + obj[U("value")] = web::json::value::string(U("test")); + conn->send(utility::conversions::to_utf8string(obj.serialize()), [&mre](std::exception_ptr exception) + { + mre.set(exception); + }); - }).get(); + mre.get(); ASSERT_FALSE(received_event->wait(2000)); @@ -64,18 +90,37 @@ TEST(connection_tests, send_message_after_connection_restart) received_event->set(); }); - conn->start().get(); + auto mre = manual_reset_event(); + conn->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); - conn->stop().get(); + conn->stop([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); - conn->start().then([conn]() + conn->start([&mre](std::exception_ptr exception) { - web::json::value obj; - obj[U("type")] = web::json::value::number(0); - obj[U("value")] = web::json::value::string(U("test")); - return conn->send(utility::conversions::to_utf8string(obj.serialize())); + mre.set(exception); + }); - }).get(); + mre.get(); + + web::json::value obj; + obj[U("type")] = web::json::value::number(0); + obj[U("value")] = web::json::value::string(U("test")); + conn->send(utility::conversions::to_utf8string(obj.serialize()), [&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); ASSERT_FALSE(received_event->wait(2000)); @@ -88,14 +133,30 @@ TEST(connection_tests, connection_id_start_stop) ASSERT_EQ("", conn->get_connection_id()); - conn->start().get(); + auto mre = manual_reset_event(); + conn->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); auto connection_id = conn->get_connection_id(); ASSERT_NE(connection_id, ""); - conn->stop().get(); + conn->stop([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); ASSERT_EQ(conn->get_connection_id(), connection_id); - conn->start().get(); + conn->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); ASSERT_NE(conn->get_connection_id(), ""); ASSERT_NE(conn->get_connection_id(), connection_id); } diff --git a/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/hub_connection_tests.cpp b/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/hub_connection_tests.cpp index f434895febf0..89d1fade27df 100644 --- a/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/hub_connection_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/hub_connection_tests.cpp @@ -7,9 +7,10 @@ #include #include "cpprest/details/basic_types.h" #include "cpprest/json.h" -#include "connection.h" -#include "hub_connection.h" -#include "signalr_exception.h" +#include "signalrclient/connection.h" +#include "signalrclient/hub_connection.h" +#include "signalrclient/signalr_exception.h" +#include "../signalrclienttests/test_utils.h" extern std::string url; @@ -18,13 +19,29 @@ TEST(hub_connection_tests, connection_status_start_stop_start) auto hub_conn = std::make_shared(url); auto weak_hub_conn = std::weak_ptr(hub_conn); - hub_conn->start().get(); + auto mre = manual_reset_event(); + hub_conn->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); ASSERT_EQ(hub_conn->get_connection_state(), signalr::connection_state::connected); - hub_conn->stop().get(); + hub_conn->stop([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); ASSERT_EQ(hub_conn->get_connection_state(), signalr::connection_state::disconnected); - hub_conn->start().get(); + hub_conn->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); ASSERT_EQ(hub_conn->get_connection_state(), signalr::connection_state::connected); } @@ -40,14 +57,23 @@ TEST(hub_connection_tests, send_message) received_event->set(); }); - hub_conn->start().then([&hub_conn]() + auto mre = manual_reset_event(); + hub_conn->start([&mre](std::exception_ptr exception) { - web::json::value obj{}; - obj[0] = web::json::value(U("test")); + mre.set(exception); + }); + + mre.get(); + + web::json::value obj{}; + obj[0] = web::json::value(U("test")); - return hub_conn->send("invokeWithString", obj); + hub_conn->send("invokeWithString", obj, [&mre](std::exception_ptr exception) + { + mre.set(exception); + }); - }).get(); + mre.get(); ASSERT_FALSE(received_event->wait(2000)); @@ -58,14 +84,30 @@ TEST(hub_connection_tests, send_message_return) { auto hub_conn = std::make_shared(url); - auto test = hub_conn->start().then([&hub_conn]() + auto mre = manual_reset_event(); + hub_conn->start([&mre](std::exception_ptr exception) { - web::json::value obj{}; - obj[0] = web::json::value(U("test")); + mre.set(exception); + }); + + mre.get(); - return hub_conn->invoke("returnString", obj); + web::json::value obj{}; + obj[0] = web::json::value(U("test")); - }).get(); + hub_conn->invoke("returnString", obj, [&mre](const web::json::value & value, std::exception_ptr exception) + { + if (exception) + { + mre.set(exception); + } + else + { + mre.set(value); + } + }); + + auto test = mre.get(); ASSERT_EQ(test.serialize(), U("\"test\"")); } @@ -82,18 +124,37 @@ TEST(hub_connection_tests, send_message_after_connection_restart) received_event->set(); }); - hub_conn->start().get(); + auto mre = manual_reset_event(); + hub_conn->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); - hub_conn->stop().get(); + mre.get(); - hub_conn->start().then([&hub_conn]() + hub_conn->stop([&mre](std::exception_ptr exception) { - web::json::value obj{}; - obj[0] = web::json::value(U("test")); + mre.set(exception); + }); - return hub_conn->send("invokeWithString", obj); + mre.get(); - }).get(); + hub_conn->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + + web::json::value obj{}; + obj[0] = web::json::value(U("test")); + + hub_conn->send("invokeWithString", obj, [&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); ASSERT_FALSE(received_event->wait(2000)); @@ -112,11 +173,21 @@ TEST(hub_connection_tests, send_message_empty_param) received_event->set(); }); - hub_conn->start().then([&hub_conn]() + auto mre = manual_reset_event(); + hub_conn->start([&mre](std::exception_ptr exception) { - return hub_conn->invoke("invokeWithEmptyParam"); + mre.set(exception); + }); - }).get(); + mre.get(); + + + hub_conn->invoke("invokeWithEmptyParam", web::json::value::array(), [&mre](const web::json::value &, std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); ASSERT_FALSE(received_event->wait(2000)); @@ -135,21 +206,29 @@ TEST(hub_connection_tests, send_message_primitive_params) received_event->set(); }); - hub_conn->start().then([&hub_conn]() + auto mre = manual_reset_event(); + hub_conn->start([&mre](std::exception_ptr exception) { - web::json::value obj{}; - obj[0] = web::json::value(5); - obj[1] = web::json::value(21.05); - obj[2] = web::json::value(8.999999999); - obj[3] = web::json::value(true); - obj[4] = web::json::value('a'); - return hub_conn->send("invokeWithPrimitiveParams", obj); + mre.set(exception); + }); - }).get(); + mre.get(); + + web::json::value obj{}; + obj[0] = web::json::value(5); + obj[1] = web::json::value(21.05); + obj[2] = web::json::value(8.999999999); + obj[3] = web::json::value(true); + obj[4] = web::json::value('a'); + hub_conn->send("invokeWithPrimitiveParams", obj, [&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); ASSERT_FALSE(received_event->wait(2000)); - web::json::value obj{}; obj[0] = web::json::value(6); obj[1] = web::json::value(22.05); obj[2] = web::json::value(9.999999999); @@ -171,21 +250,30 @@ TEST(hub_connection_tests, send_message_complex_type) received_event->set(); }); - hub_conn->start().then([&hub_conn]() + auto mre = manual_reset_event(); + hub_conn->start([&mre](std::exception_ptr exception) { - web::json::value obj{}; - web::json::value person; - web::json::value address; - address[U("street")] = web::json::value::string(U("main st")); - address[U("zip")] = web::json::value::string(U("98052")); - person[U("address")] = address; - person[U("name")] = web::json::value::string(U("test")); - person[U("age")] = web::json::value::number(15); - obj[0] = person; + mre.set(exception); + }); - return hub_conn->send("invokeWithComplexType", obj); + mre.get(); - }).get(); + web::json::value obj{}; + web::json::value person; + web::json::value address; + address[U("street")] = web::json::value::string(U("main st")); + address[U("zip")] = web::json::value::string(U("98052")); + person[U("address")] = address; + person[U("name")] = web::json::value::string(U("test")); + person[U("age")] = web::json::value::number(15); + obj[0] = person; + + hub_conn->send("invokeWithComplexType", obj, [&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); ASSERT_FALSE(received_event->wait(2000)); @@ -196,21 +284,37 @@ TEST(hub_connection_tests, send_message_complex_type_return) { auto hub_conn = std::make_shared(url); - auto test = hub_conn->start().then([&hub_conn]() + auto mre = manual_reset_event(); + hub_conn->start([&mre](std::exception_ptr exception) { - web::json::value obj{}; - web::json::value person; - web::json::value address; - address[U("street")] = web::json::value::string(U("main st")); - address[U("zip")] = web::json::value::string(U("98052")); - person[U("address")] = address; - person[U("name")] = web::json::value::string(U("test")); - person[U("age")] = web::json::value::number(15); - obj[0] = person; + mre.set(exception); + }); - return hub_conn->invoke("returnComplexType", obj); + mre.get(); + + web::json::value obj{}; + web::json::value person; + web::json::value address; + address[U("street")] = web::json::value::string(U("main st")); + address[U("zip")] = web::json::value::string(U("98052")); + person[U("address")] = address; + person[U("name")] = web::json::value::string(U("test")); + person[U("age")] = web::json::value::number(15); + obj[0] = person; + + hub_conn->invoke("returnComplexType", obj, [&mre](const web::json::value & value, std::exception_ptr exception) + { + if (exception) + { + mre.set(exception); + } + else + { + mre.set(value); + } + }); - }).get(); + auto test = mre.get(); ASSERT_EQ(test.serialize(), U("{\"Address\":{\"Street\":\"main st\",\"Zip\":\"98052\"},\"Age\":15,\"Name\":\"test\"}")); } @@ -224,14 +328,31 @@ TEST(hub_connection_tests, connection_id_start_stop_start) ASSERT_EQ(u8"", hub_conn->get_connection_id()); - hub_conn->start().get(); + auto mre = manual_reset_event(); + hub_conn->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); + connection_id = hub_conn->get_connection_id(); ASSERT_NE(connection_id, u8""); - hub_conn->stop().get(); + hub_conn->stop([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); ASSERT_EQ(hub_conn->get_connection_id(), connection_id); - hub_conn->start().get(); + hub_conn->start([&mre](std::exception_ptr exception) + { + mre.set(exception); + }); + + mre.get(); ASSERT_NE(hub_conn->get_connection_id(), u8""); ASSERT_NE(hub_conn->get_connection_id(), connection_id); diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h index a34983874e1f..bc3daf8d7dec 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h @@ -5,7 +5,6 @@ #include "cpprest/details/basic_types.h" #include "signalrclient/websocket_client.h" -#include "web_request_factory.h" #include "signalrclient/http_client.h" #include @@ -17,7 +16,6 @@ std::shared_ptr create_test_websocket_client( std::function)> connect_function = [](const std::string&, std::function callback) { callback(nullptr); }, std::function)> close_function = [](std::function callback) { callback(nullptr); }); -std::unique_ptr create_test_web_request_factory(); std::unique_ptr create_test_http_client(); std::string create_uri(); std::string create_uri(const std::string& query_string); From 2ad902d988dd2fbc974ac185781137a7b8851871 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Fri, 29 Mar 2019 08:54:38 -0700 Subject: [PATCH 8/9] fb --- .../HubConnectionSample.cpp | 2 +- .../cpp/src/signalrclient/hub_connection.cpp | 36 +++++++++++++++++-- .../src/signalrclient/hub_connection_impl.cpp | 12 +++++-- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp b/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp index d12de18c7b25..dba7bcc9644f 100644 --- a/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp +++ b/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp @@ -57,7 +57,7 @@ void chat() { try { - + std::rethrow_exception(exception); } catch (const std::exception & ex) { diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp b/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp index 9d105c81a61e..c1f68dab6b4d 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp @@ -19,11 +19,21 @@ namespace signalr void hub_connection::start(std::function callback) noexcept { + if (!m_pImpl) + { + callback(std::make_exception_ptr(signalr_exception("start() cannot be called on destructed hub_connection instance"))); + } + m_pImpl->start(callback); } void hub_connection::stop(std::function callback) noexcept { + if (!m_pImpl) + { + callback(std::make_exception_ptr(signalr_exception("stop() cannot be called on destructed hub_connection instance"))); + } + m_pImpl->stop(callback); } @@ -31,7 +41,7 @@ namespace signalr { if (!m_pImpl) { - throw signalr_exception("on() cannot be called on uninitialized hub_connection instance"); + throw signalr_exception("on() cannot be called on destructed hub_connection instance"); } return m_pImpl->on(event_name, handler); @@ -41,7 +51,7 @@ namespace signalr { if (!m_pImpl) { - callback(web::json::value(), std::make_exception_ptr(signalr_exception("invoke() cannot be called on uninitialized hub_connection instance"))); + callback(web::json::value(), std::make_exception_ptr(signalr_exception("invoke() cannot be called on destructed hub_connection instance"))); return; } @@ -52,7 +62,7 @@ namespace signalr { if (!m_pImpl) { - callback(std::make_exception_ptr(signalr_exception("send() cannot be called on uninitialized hub_connection instance"))); + callback(std::make_exception_ptr(signalr_exception("send() cannot be called on destructed hub_connection instance"))); return; } @@ -61,21 +71,41 @@ namespace signalr connection_state hub_connection::get_connection_state() const { + if (!m_pImpl) + { + throw signalr_exception("get_connection_state() cannot be called on destructed hub_connection instance"); + } + return m_pImpl->get_connection_state(); } std::string hub_connection::get_connection_id() const { + if (!m_pImpl) + { + throw signalr_exception("get_connection_id() cannot be called on destructed hub_connection instance"); + } + return m_pImpl->get_connection_id(); } void hub_connection::set_disconnected(const std::function& disconnected_callback) { + if (!m_pImpl) + { + throw signalr_exception("set_disconnected() cannot be called on destructed hub_connection instance"); + } + m_pImpl->set_disconnected(disconnected_callback); } void hub_connection::set_client_config(const signalr_client_config& config) { + if (!m_pImpl) + { + throw signalr_exception("set_client_config() cannot be called on destructed hub_connection instance"); + } + m_pImpl->set_client_config(config); } } diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp index 9676ba997107..625b07dad11c 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp @@ -121,10 +121,16 @@ namespace signalr if (start_exception) { - connection->m_connection->stop([start_exception, callback, connection](std::exception_ptr) + connection->m_connection->stop([start_exception, callback, weak_connection](std::exception_ptr) { try { + auto connection = weak_connection.lock(); + if (!connection) + { + callback(std::make_exception_ptr(signalr_exception("the hub connection has been deconstructed"))); + return; + } pplx::task(connection->m_handshakeTask).get(); } catch (...) {} @@ -134,7 +140,9 @@ namespace signalr return; } - connection->m_connection->send("{\"protocol\":\"json\",\"version\":1}\x1e", [weak_connection, callback](std::exception_ptr exception) + // TODO: Generate this later when we have the protocol abstraction + auto handshake_request = "{\"protocol\":\"json\",\"version\":1}\x1e"; + connection->m_connection->send(handshake_request, [weak_connection, callback](std::exception_ptr exception) { auto connection = weak_connection.lock(); if (!connection) From cf894f150b721e1ee7f62337b6c475f63ebf0d6a Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Fri, 29 Mar 2019 14:41:32 -0700 Subject: [PATCH 9/9] fb --- .../clients/cpp/src/signalrclient/hub_connection_impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp index 625b07dad11c..b0ea676006cb 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp @@ -397,7 +397,7 @@ namespace signalr } else { - set_result(json::value::null()); + set_result(json::value::value()); } }; }