ILogStuff - enabling logging from `connection_impl` and transports

Note that a side effect of this change is that we now will be able to raise events from transports since transports now will have a (smart) pointer to the connection_impl instance.
This commit is contained in:
moozzyk 2014-11-11 13:57:52 -08:00
Родитель ac5a9e0a4d
Коммит 78ee26b990
18 изменённых файлов: 342 добавлений и 71 удалений

Просмотреть файл

@ -7,6 +7,9 @@
#include "_exports.h"
#include "transport_type.h"
#include "connection_state.h"
#include "trace_level.h"
#include "log_writer.h"
#include "trace_log_writer.h"
namespace signalr
{
@ -15,7 +18,8 @@ namespace signalr
class connection
{
public:
explicit connection(const utility::string_t& url, const utility::string_t& querystring = U(""));
explicit connection(const utility::string_t& url, const utility::string_t& querystring = U(""),
trace_level trace_level = trace_level::all, std::shared_ptr<log_writer> log_writer = std::make_shared<trace_log_writer>());
// TODO: consider making connection class copyable to enable passing by value
connection(const connection&) = delete;

Просмотреть файл

@ -8,8 +8,8 @@
namespace signalr
{
connection::connection(const utility::string_t& url, const utility::string_t& querystring)
: m_pImpl(connection_impl::create(url, querystring))
connection::connection(const utility::string_t& url, const utility::string_t& querystring, trace_level trace_level, std::shared_ptr<log_writer> log_writer)
: m_pImpl(connection_impl::create(url, querystring, trace_level, log_writer))
{}
// Do NOT remove this destructor. Letting the compiler generate and inline the default dtor may lead to

Просмотреть файл

@ -8,20 +8,21 @@
namespace signalr
{
std::shared_ptr<connection_impl> connection_impl::create(const utility::string_t& url, const utility::string_t& querystring)
{
return connection_impl::create(url, querystring, std::make_unique<web_request_factory>(), std::make_unique<transport_factory>());
}
std::shared_ptr<connection_impl> connection_impl::create(const utility::string_t& url, const utility::string_t& querystring,
std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory)
trace_level trace_level, std::shared_ptr<log_writer> log_writer)
{
return std::shared_ptr<connection_impl>(new connection_impl(url, querystring, std::move(web_request_factory), std::move(transport_factory)));
return connection_impl::create(url, querystring, trace_level, log_writer, std::make_unique<web_request_factory>(), std::make_unique<transport_factory>());
}
connection_impl::connection_impl(const utility::string_t& url, const utility::string_t& querystring,
std::shared_ptr<connection_impl> connection_impl::create(const utility::string_t& url, const utility::string_t& querystring, trace_level trace_level,
std::shared_ptr<log_writer> log_writer, std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory)
{
return std::shared_ptr<connection_impl>(new connection_impl(url, querystring, trace_level, log_writer, std::move(web_request_factory), std::move(transport_factory)));
}
connection_impl::connection_impl(const utility::string_t& url, const utility::string_t& querystring, trace_level trace_level, std::shared_ptr<log_writer> log_writer,
std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory)
: m_base_uri(url), m_querystring(querystring), m_web_request_factory(std::move(web_request_factory)),
: m_base_uri(url), m_querystring(querystring), m_logger(log_writer, trace_level), m_web_request_factory(std::move(web_request_factory)),
m_transport_factory(std::move(transport_factory)), m_connection_state(std::move(connection_state::disconnected))
{ }
@ -41,17 +42,47 @@ namespace signalr
return m_connection_state.load();
}
logger connection_impl::get_logger() const
{
return m_logger;
}
bool connection_impl::change_state(connection_state old_state, connection_state new_state)
{
connection_state expected_state{ old_state };
if (!m_connection_state.compare_exchange_strong(expected_state, new_state, std::memory_order_seq_cst))
if (m_connection_state.compare_exchange_strong(expected_state, new_state, std::memory_order_seq_cst))
{
// TODO: add logging
m_logger.log(
trace_level::state_changes,
utility::string_t(_XPLATSTR("state changed: "))
.append(translate_connection_state(old_state))
.append(_XPLATSTR(" -> "))
.append(translate_connection_state(new_state)));
// TODO: invoke state_changed callback
return false;
return true;
}
return true;
return false;
}
utility::string_t connection_impl::translate_connection_state(connection_state state)
{
switch (state)
{
case connection_state::connecting:
return _XPLATSTR("connecting");
case connection_state::connected:
return _XPLATSTR("connected");
case connection_state::reconnecting:
return _XPLATSTR("reconnecting");
case connection_state::disconnected:
return _XPLATSTR("disconnected");
default:
_ASSERTE(false);
return _XPLATSTR("(unknown)");
}
}
}

Просмотреть файл

@ -21,10 +21,11 @@ namespace signalr
class connection_impl : public std::enable_shared_from_this<connection_impl>
{
public:
static std::shared_ptr<connection_impl> create(const utility::string_t& url, const utility::string_t& querystring);
static std::shared_ptr<connection_impl> create(const utility::string_t& url, const utility::string_t& querystring,
std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory);
trace_level trace_level, std::shared_ptr<log_writer> log_writer);
static std::shared_ptr<connection_impl> create(const utility::string_t& url, const utility::string_t& querystring, trace_level trace_level,
std::shared_ptr<log_writer> log_writer, std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory);
connection_impl(const connection_impl&) = delete;
@ -34,17 +35,21 @@ namespace signalr
connection_state get_connection_state() const;
logger get_logger() const;
private:
web::uri m_base_uri;
utility::string_t m_querystring;
std::atomic<connection_state> m_connection_state;
logger m_logger;
std::unique_ptr<web_request_factory> m_web_request_factory;
std::unique_ptr<transport_factory> m_transport_factory;
connection_impl(const utility::string_t& url, const utility::string_t& querystring,
connection_impl(const utility::string_t& url, const utility::string_t& querystring, trace_level trace_level, std::shared_ptr<log_writer> log_writer,
std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory);
bool change_state(connection_state old_state, connection_state new_state);
utility::string_t translate_connection_state(connection_state state);
};
}

Просмотреть файл

@ -16,9 +16,21 @@ namespace signalr
{
if ((level & m_trace_level) != trace_level::none)
{
utility::ostringstream_t os;
os << utility::datetime::utc_now().to_string(utility::datetime::date_format::ISO_8601) << _XPLATSTR(" ") << entry << std::endl;
m_log_writer->write(os.str());
try
{
utility::ostringstream_t os;
os << utility::datetime::utc_now().to_string(utility::datetime::date_format::ISO_8601) << _XPLATSTR(" ") << entry << std::endl;
m_log_writer->write(os.str());
}
catch (const std::exception &e)
{
ucerr << _XPLATSTR("error occurred when logging: ") << utility::conversions::to_string_t(e.what())
<< std::endl << _XPLATSTR(" entry: ") << entry << std::endl;
}
catch (...)
{
ucerr << _XPLATSTR("unknown error occurred when logging") << std::endl << _XPLATSTR(" entry: ") << entry << std::endl;
}
}
}
}

Просмотреть файл

@ -4,14 +4,15 @@
#include "stdafx.h"
#include "transport_factory.h"
#include "websocket_transport.h"
#include "connection_impl.h"
namespace signalr
{
std::unique_ptr<transport> transport_factory::create_transport(transport_type transport_type)
std::unique_ptr<transport> transport_factory::create_transport(transport_type transport_type, std::shared_ptr<connection_impl> connection)
{
if (transport_type == transport_type::websockets)
{
return std::make_unique<websocket_transport>(std::make_unique<default_websocket_client>());
return std::make_unique<websocket_transport>(std::make_unique<default_websocket_client>(), connection);
}
throw std::exception("not implemented");

Просмотреть файл

@ -9,10 +9,12 @@
namespace signalr
{
class connection_impl;
class transport_factory
{
public:
std::unique_ptr<transport> virtual create_transport(transport_type transport_type);
virtual std::unique_ptr<transport> create_transport(transport_type transport_type, std::shared_ptr<connection_impl> connection);
virtual ~transport_factory();
};

Просмотреть файл

@ -3,16 +3,17 @@
#include "stdafx.h"
#include "websocket_transport.h"
#include "logger.h"
namespace signalr
{
namespace
{
void receive_loop(std::shared_ptr<websocket_client> websocket_client, pplx::cancellation_token_source cts);
void receive_loop(std::shared_ptr<websocket_client> websocket_client, std::shared_ptr<connection_impl> connection, pplx::cancellation_token_source cts);
}
websocket_transport::websocket_transport(std::shared_ptr<websocket_client> websocket_client)
: m_websocket_client(websocket_client)
websocket_transport::websocket_transport(std::shared_ptr<websocket_client> websocket_client, std::shared_ptr<connection_impl> connection)
: m_websocket_client(websocket_client), m_connection(connection)
{
// we use this cts to check if the receive loop is running so it should be
// initially cancelled to indicate that the receive loop is not running
@ -41,21 +42,27 @@ namespace signalr
pplx::cancellation_token_source receive_loop_cts;
std::shared_ptr<websocket_client> websocket_client(m_websocket_client);
pplx::task_completion_event<void> connect_tce;
auto connection = m_connection;
m_websocket_client->connect(url)
.then([websocket_client, connect_tce, receive_loop_cts](pplx::task<void> connect_task)
.then([websocket_client, connect_tce, receive_loop_cts, connection](pplx::task<void> connect_task)
{
try
{
connect_task.get();
receive_loop(websocket_client, receive_loop_cts);
receive_loop(websocket_client, connection, receive_loop_cts);
connect_tce.set();
}
catch (const std::exception &e)
{
// TODO: logging, on error(?) - see what we do in the .net client
connection->get_logger().log(
trace_level::messages,
utility::string_t(_XPLATSTR("[websocket transport] exception when connecting to the server: "))
.append(utility::conversions::to_string_t(e.what())));
// TODO: on error(?) - see what we do in the .net client
receive_loop_cts.cancel();
connect_tce.set_exception(e);
connect_tce.set_exception(std::current_exception());
}
});
@ -74,27 +81,36 @@ namespace signalr
{
m_receive_loop_cts.cancel();
auto logger = m_connection->get_logger();
return m_websocket_client->close()
.then([](pplx::task<void> close_task)
{
.then([logger](pplx::task<void> close_task)
mutable {
try
{
close_task.get();
}
catch (const std::exception &)
catch (const std::exception &e)
{
// TODO:log
logger.log(
trace_level::messages,
utility::string_t(_XPLATSTR("[websocket transport] exception when closing websocket: "))
.append(utility::conversions::to_string_t(e.what())));
}
});
}
// unnamed namespace makes this function invisible for other translation units
// unnamed namespace makes this function invisible/unusable in other translation units
namespace
{
void receive_loop(std::shared_ptr<websocket_client> websocket_client, pplx::cancellation_token_source cts)
void receive_loop(std::shared_ptr<websocket_client> websocket_client, std::shared_ptr<connection_impl> connection, pplx::cancellation_token_source cts)
{
websocket_client->receive()
.then([websocket_client, cts](pplx::task<std::string> receive_task)
// There are two cases when we exit the loop. The first case is implicit - we pass the cancellation_token
// to `then` (note this is after the lambda body) and if the token is cancelled the continuation will not
// run at all. The second - explicit - case happens if the token gets cancelled after the continuation has
// been started in which case we just stop the loop by not scheduling another receive task.
.then([websocket_client, connection, cts](pplx::task<std::string> receive_task)
{
try
{
@ -104,29 +120,41 @@ namespace signalr
if (!pplx::is_task_cancellation_requested())
{
receive_loop(websocket_client, cts);
receive_loop(websocket_client, connection, cts);
}
// TODO: `else` to log that loop has been cancelled?
return;
}
// TODO: log, report error, close websocket (when appropriate)
catch (const web_sockets::client::websocket_exception&)
// TODO: report error, close websocket (when appropriate)
catch (const web_sockets::client::websocket_exception& e)
{
connection->get_logger().log(
trace_level::messages,
utility::string_t(_XPLATSTR("[websocket transport] websocket exception when receiving data: "))
.append(utility::conversions::to_string_t(e.what())));
}
catch (const pplx::task_canceled &)
catch (const pplx::task_canceled& e)
{
connection->get_logger().log(
trace_level::messages,
utility::string_t(_XPLATSTR("[websocket transport] receive task cancelled: "))
.append(utility::conversions::to_string_t(e.what())));
}
catch (const std::exception&)
catch (const std::exception& e)
{
connection->get_logger().log(
trace_level::messages,
utility::string_t(_XPLATSTR("[websocket transport] error receiving response from websocket: "))
.append(utility::conversions::to_string_t(e.what())));
}
catch (...)
{
connection->get_logger().log(
trace_level::messages,
utility::string_t(_XPLATSTR("[websocket transport] unknown error occurred when receiving response from websocket")));
}
cts.cancel();
}, cts.get_token());
}
}

Просмотреть файл

@ -8,14 +8,14 @@
#include "transport.h"
#include "logger.h"
#include "default_websocket_client.h"
#include "connection_impl.h"
namespace signalr
{
class websocket_transport : public transport
{
public:
websocket_transport(std::shared_ptr<websocket_client> websocket_client);
websocket_transport(std::shared_ptr<websocket_client> websocket_client, std::shared_ptr<connection_impl> connection);
~websocket_transport();
@ -31,6 +31,7 @@ namespace signalr
private:
std::shared_ptr<websocket_client> m_websocket_client;
std::shared_ptr<connection_impl> m_connection;
pplx::cancellation_token_source m_receive_loop_cts;
};

Просмотреть файл

@ -88,6 +88,7 @@
<ClInclude Include="..\..\memory_log_writer.h" />
<ClInclude Include="..\..\stdafx.h" />
<ClInclude Include="..\..\targetver.h" />
<ClInclude Include="..\..\test_utils.h" />
<ClInclude Include="..\..\test_websocket_client.h" />
<ClInclude Include="..\..\test_web_request_factory.h" />
<ClInclude Include="..\..\web_request_stub.h" />
@ -103,6 +104,7 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\..\test_utils.cpp" />
<ClCompile Include="..\..\test_websocket_client.cpp" />
<ClCompile Include="..\..\test_web_request_factory.cpp" />
<ClCompile Include="..\..\url_builder_tests.cpp" />

Просмотреть файл

@ -33,6 +33,9 @@
<ClInclude Include="..\..\memory_log_writer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\test_utils.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\stdafx.cpp">
@ -74,6 +77,9 @@
<ClCompile Include="..\..\memory_log_writer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\test_utils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

Просмотреть файл

@ -2,6 +2,7 @@
// 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 "connection_impl.h"
#include "signalrclient\trace_level.h"
#include "signalrclient\trace_log_writer.h"
@ -13,14 +14,17 @@ using namespace signalr;
TEST(connection_impl_connection_state, initial_connection_state_is_disconnected)
{
auto connection = connection_impl::create(_XPLATSTR("url"), _XPLATSTR(""));
auto connection =
connection_impl::create(_XPLATSTR("url"), _XPLATSTR(""), trace_level::none, std::make_shared<trace_log_writer>());
ASSERT_EQ(connection_state::disconnected, connection->get_connection_state());
}
TEST(connection_impl_start, cannot_start_non_disconnected_exception)
{
auto connection = connection_impl::create(_XPLATSTR("url"), _XPLATSTR(""));
auto connection =
connection_impl::create(_XPLATSTR("url"), _XPLATSTR(""), trace_level::none, std::make_shared<trace_log_writer>());
connection->start().wait();
try
@ -32,14 +36,31 @@ TEST(connection_impl_start, cannot_start_non_disconnected_exception)
{
ASSERT_EQ(
_XPLATSTR("cannot start a connection that is not in the disconnected state"),
utility::conversions::print_string(e.what()));
utility::conversions::to_string_t(e.what()));
}
}
TEST(connection_impl_start, connection_state_is_connecting_when_connection_is_being_started)
{
auto connection = connection_impl::create(_XPLATSTR("url"), _XPLATSTR(""));
auto connection =
connection_impl::create(_XPLATSTR("url"), _XPLATSTR(""), trace_level::none, std::make_shared<trace_log_writer>());
connection->start().wait();
ASSERT_EQ(connection->get_connection_state(), connection_state::connecting);
}
TEST(connection_impl_change_state, change_state_logs)
{
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
auto connection =
connection_impl::create(_XPLATSTR("url"), _XPLATSTR(""), trace_level::state_changes, writer);
connection->start().wait();
auto log_entries = std::dynamic_pointer_cast<memory_log_writer>(writer)->get_log_entries();
ASSERT_FALSE(log_entries.empty());
auto entry = remove_date_from_log_entry(log_entries[0]);
ASSERT_EQ(_XPLATSTR("state changed: disconnected -> connecting\n"), entry);
}

Просмотреть файл

@ -31,7 +31,7 @@ TEST(http_sender_get_response, exception_thrown_if_status_code_not_200)
{
ASSERT_EQ(
_XPLATSTR("web exception: 404 ") + response_phrase,
utility::conversions::print_string(e.what()));
utility::conversions::to_string_t(e.what()));
}
}

Просмотреть файл

@ -2,6 +2,7 @@
// 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 <cpprest\asyncrt_utils.h>
#include "signalrclient\trace_log_writer.h"
#include "logger.h"
@ -29,7 +30,7 @@ TEST(logger_write, entry_not_added_if_trace_level_not_set)
auto log_entries = std::dynamic_pointer_cast<memory_log_writer>(writer)->get_log_entries();
ASSERT_EQ(0, log_entries.size());
ASSERT_TRUE(log_entries.empty());
}
TEST(logger_write, entries_added_for_combined_trace_level)
@ -53,11 +54,14 @@ TEST(logger_write, entries_formatted_correctly)
logger l(writer, trace_level::all);
l.log(trace_level::messages, _XPLATSTR("message"));
auto entry = std::dynamic_pointer_cast<memory_log_writer>(writer)->get_log_entries()[0];
auto log_entries = std::dynamic_pointer_cast<memory_log_writer>(writer)->get_log_entries();
ASSERT_FALSE(log_entries.empty());
auto date_str = entry.substr(0, 28);
auto entry = log_entries[0];
auto date_str = entry.substr(0, entry.find_first_of(_XPLATSTR("Z")) + 1);
auto date = utility::datetime::from_string(date_str, utility::datetime::ISO_8601);
ASSERT_EQ(date_str, date.to_string(utility::datetime::ISO_8601));
ASSERT_EQ(_XPLATSTR(" message\n"), entry.substr(28));
ASSERT_EQ(_XPLATSTR("message\n"), remove_date_from_log_entry(entry));
}

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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 "cpprest\basic_types.h"
utility::string_t remove_date_from_log_entry(const utility::string_t &log_entry)
{
// dates are ISO 8601 (e.g. `2014-11-13T06:05:29.452066Z`)
auto date_end_index = log_entry.find_first_of(_XPLATSTR("Z")) + 1;
// date is followed by a whitespace hence +1
return log_entry.substr(date_end_index + 1);
}

Просмотреть файл

@ -0,0 +1,8 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#pragma once
#include <cpprest\basic_types.h>
utility::string_t remove_date_from_log_entry(const utility::string_t &log_entry);

Просмотреть файл

@ -2,8 +2,11 @@
// 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 "signalrclient\trace_log_writer.h"
#include "test_websocket_client.h"
#include "websocket_transport.h"
#include "memory_log_writer.h"
using namespace signalr;
using namespace web::experimental;
@ -26,7 +29,8 @@ TEST(websocket_transport_connect, connect_connects_and_starts_receive_loop)
return pplx::task_from_result(std::string(""));
});
websocket_transport ws_transport(client);
websocket_transport ws_transport{ client, connection_impl::create(_XPLATSTR("http://fake.uri"), _XPLATSTR(""),
trace_level::none, std::make_shared<trace_log_writer>()) };
ws_transport.connect(_XPLATSTR("http://fakeuri.org")).get();
@ -37,28 +41,61 @@ TEST(websocket_transport_connect, connect_connects_and_starts_receive_loop)
TEST(websocket_transport_connect, connect_propagates_exceptions)
{
auto client = std::make_shared<test_websocket_client>();
client->set_connect_function([](const web::uri &) -> pplx::task<void>
client->set_connect_function([](const web::uri &)->pplx::task<void>
{
throw web_sockets::client::websocket_exception(_XPLATSTR("connecting failed"));
return pplx::task_from_exception<void>(web_sockets::client::websocket_exception(_XPLATSTR("connecting failed")));
});
websocket_transport ws_transport(client);
websocket_transport ws_transport{ client, connection_impl::create(_XPLATSTR("http://fake.uri"),
_XPLATSTR(""), trace_level::none, std::make_shared<trace_log_writer>()) };
try
{
ws_transport.connect(_XPLATSTR("http://fakeuri.org")).wait();
ws_transport.connect(_XPLATSTR("http://fakeuri.org")).get();
ASSERT_TRUE(false); // exception not thrown
}
catch (const std::exception &e)
{
ASSERT_STREQ("connecting failed", e.what());
ASSERT_EQ(_XPLATSTR("connecting failed"), utility::conversions::to_string_t(e.what()));
}
}
TEST(websocket_transport_connect, connect_logs_exceptions)
{
auto client = std::make_shared<test_websocket_client>();
client->set_connect_function([](const web::uri &) -> pplx::task<void>
{
return pplx::task_from_exception<void>(web_sockets::client::websocket_exception(_XPLATSTR("connecting failed")));
});
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
websocket_transport ws_transport{ client, connection_impl::create(_XPLATSTR("http://fake.uri"),
_XPLATSTR(""), trace_level::messages, writer) };
try
{
ws_transport.connect(_XPLATSTR("http://fakeuri.org")).wait();
}
catch (...)
{ }
auto log_entries = std::dynamic_pointer_cast<memory_log_writer>(writer)->get_log_entries();
ASSERT_FALSE(log_entries.empty());
auto entry = remove_date_from_log_entry(log_entries[0]);
ASSERT_EQ(
_XPLATSTR("[websocket transport] exception when connecting to the server: connecting failed\n"),
entry);
}
TEST(websocket_transport_connect, cannot_call_connect_on_already_connected_transport)
{
auto client = std::make_shared<test_websocket_client>();
websocket_transport ws_transport(client);
websocket_transport ws_transport{ client, connection_impl::create(_XPLATSTR("http://fake.uri"),
_XPLATSTR(""), trace_level::none, std::make_shared<trace_log_writer>()) };
ws_transport.connect(_XPLATSTR("http://fakeuri.org")).wait();
@ -69,7 +106,7 @@ TEST(websocket_transport_connect, cannot_call_connect_on_already_connected_trans
}
catch (const std::exception &e)
{
ASSERT_STREQ("transport already connected", e.what());
ASSERT_EQ(_XPLATSTR("transport already connected"), utility::conversions::to_string_t(e.what()));
}
}
@ -77,7 +114,8 @@ TEST(websocket_transport_connect, can_connect_after_disconnecting)
{
auto client = std::make_shared<test_websocket_client>();
websocket_transport ws_transport(client);
websocket_transport ws_transport{ client, connection_impl::create(_XPLATSTR("http://fake.uri"),
_XPLATSTR(""), trace_level::none, std::make_shared<trace_log_writer>()) };
ws_transport.connect(_XPLATSTR("http://fakeuri.org")).get();
ws_transport.disconnect().get();
@ -97,7 +135,8 @@ TEST(websocket_transport_send, send_creates_and_sends_websocket_messages)
return pplx::task_from_result();
});
websocket_transport ws_transport(client);
websocket_transport ws_transport{ client, connection_impl::create(_XPLATSTR("http://fake.uri"),
_XPLATSTR(""), trace_level::none, std::make_shared<trace_log_writer>()) };
ws_transport.send(_XPLATSTR("ABC")).wait();
@ -116,7 +155,8 @@ TEST(websocket_transport_disconnect, disconnect_closes_websocket)
return pplx::task_from_result();
});
websocket_transport ws_transport(client);
websocket_transport ws_transport{ client, connection_impl::create(_XPLATSTR("http://fake.uri"),
_XPLATSTR(""), trace_level::none, std::make_shared<trace_log_writer>()) };
ws_transport.disconnect().get();
@ -132,10 +172,42 @@ TEST(websocket_transport_disconnect, disconnect_does_not_throw)
return pplx::task_from_exception<void>(std::exception());
});
websocket_transport ws_transport(client);
websocket_transport ws_transport{ client, connection_impl::create(_XPLATSTR("http://fake.uri"),
_XPLATSTR(""), trace_level::none, std::make_shared<trace_log_writer>()) };
ws_transport.disconnect().get();
}
TEST(websocket_transport_disconnect, disconnect_logs_exceptions)
{
auto client = std::make_shared<test_websocket_client>();
client->set_close_function([]()->pplx::task<void>
{
return pplx::task_from_exception<void>(web_sockets::client::websocket_exception(_XPLATSTR("connection closing failed")));
});
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
websocket_transport ws_transport{client, connection_impl::create(_XPLATSTR("http://fake.uri"),
_XPLATSTR(""), trace_level::messages, writer)};
try
{
ws_transport.disconnect().get();
}
catch (...)
{}
auto log_entries = std::dynamic_pointer_cast<memory_log_writer>(writer)->get_log_entries();
ASSERT_FALSE(log_entries.empty());
auto entry = remove_date_from_log_entry(log_entries[0]);
ASSERT_EQ(
_XPLATSTR("[websocket transport] exception when closing websocket: connection closing failed\n"),
entry);
}
TEST(websocket_transport_disconnect, receive_not_called_after_disconnect)
{
auto client = std::make_shared<test_websocket_client>();
@ -157,7 +229,8 @@ TEST(websocket_transport_disconnect, receive_not_called_after_disconnect)
return pplx::create_task(receive_task_tce);
});
websocket_transport ws_transport(client);
websocket_transport ws_transport{ client, connection_impl::create(_XPLATSTR("http://fake.uri"),
_XPLATSTR(""), trace_level::none, std::make_shared<trace_log_writer>()) };
ws_transport.connect(_XPLATSTR("http://fakeuri.org")).get();
ws_transport.disconnect().get();
@ -168,3 +241,62 @@ TEST(websocket_transport_disconnect, receive_not_called_after_disconnect)
ASSERT_EQ(2, num_called);
}
template<class T>
void receive_loop_logs_exception_runner(const T& e, const utility::string_t& expected_message);
TEST(websocket_transport_receive_loop, receive_loop_logs_websocket_exceptions)
{
receive_loop_logs_exception_runner(
web_sockets::client::websocket_exception(_XPLATSTR("receive failed")),
_XPLATSTR("[websocket transport] websocket exception when receiving data: receive failed\n"));
}
TEST(websocket_transport_receive_loop, receive_loop_logs_if_receive_task_cancelled)
{
receive_loop_logs_exception_runner(
pplx::task_canceled("cancelled"),
_XPLATSTR("[websocket transport] receive task cancelled: cancelled\n"));
}
TEST(websocket_transport_receive_loop, receive_loop_logs_std_exception)
{
receive_loop_logs_exception_runner(
std::exception("exception"),
_XPLATSTR("[websocket transport] error receiving response from websocket: exception\n"));
}
template<class T>
void receive_loop_logs_exception_runner(const T& e, const utility::string_t& expected_message)
{
pplx::event receive_event;
auto client = std::make_shared<test_websocket_client>();
client->set_receive_function([&receive_event, &e]()->pplx::task<std::string>
{
receive_event.set();
return pplx::task_from_exception<std::string>(e);
});
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
websocket_transport ws_transport{ client, connection_impl::create(_XPLATSTR("http://fake.uri"),
_XPLATSTR(""), trace_level::messages, writer) };
ws_transport.connect(_XPLATSTR("url"))
.then([&receive_event]()
{
receive_event.wait();
}).get();
// this is race'y but there is nothing we can block on
pplx::wait(10);
auto log_entries = std::dynamic_pointer_cast<memory_log_writer>(writer)->get_log_entries();
ASSERT_FALSE(log_entries.empty());
auto entry = remove_date_from_log_entry(log_entries[0]);
ASSERT_EQ(expected_message, entry);
}