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 "_exports.h"
#include "transport_type.h" #include "transport_type.h"
#include "connection_state.h" #include "connection_state.h"
#include "trace_level.h"
#include "log_writer.h"
#include "trace_log_writer.h"
namespace signalr namespace signalr
{ {
@ -15,7 +18,8 @@ namespace signalr
class connection class connection
{ {
public: 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 // TODO: consider making connection class copyable to enable passing by value
connection(const connection&) = delete; connection(const connection&) = delete;

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

@ -5,7 +5,7 @@
#include <stdexcept> #include <stdexcept>
#include <cpprest\basic_types.h> #include <cpprest\basic_types.h>
#include <cpprest\asyncrt_utils.h> #include <cpprest\asyncrt_utils.h>
namespace signalr namespace signalr
{ {

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

@ -8,8 +8,8 @@
namespace signalr namespace signalr
{ {
connection::connection(const utility::string_t& url, const utility::string_t& 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)) : 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 // Do NOT remove this destructor. Letting the compiler generate and inline the default dtor may lead to

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

@ -8,20 +8,21 @@
namespace signalr namespace signalr
{ {
std::shared_ptr<connection_impl> connection_impl::create(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)
{ {
return connection_impl::create(url, querystring, std::make_unique<web_request_factory>(), std::make_unique<transport_factory>()); return connection_impl::create(url, querystring, trace_level, log_writer, 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::shared_ptr<connection_impl> connection_impl::create(const utility::string_t& url, const utility::string_t& querystring, trace_level trace_level,
std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory) 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, std::move(web_request_factory), std::move(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, 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) 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)) 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(); 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) bool connection_impl::change_state(connection_state old_state, connection_state new_state)
{ {
connection_state expected_state{ old_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 // 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> class connection_impl : public std::enable_shared_from_this<connection_impl>
{ {
public: 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,
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, static std::shared_ptr<connection_impl> create(const utility::string_t& url, const utility::string_t& querystring, trace_level trace_level,
std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory); 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; connection_impl(const connection_impl&) = delete;
@ -34,17 +35,21 @@ namespace signalr
connection_state get_connection_state() const; connection_state get_connection_state() const;
logger get_logger() const;
private: private:
web::uri m_base_uri; web::uri m_base_uri;
utility::string_t m_querystring; utility::string_t m_querystring;
std::atomic<connection_state> m_connection_state; std::atomic<connection_state> m_connection_state;
logger m_logger;
std::unique_ptr<web_request_factory> m_web_request_factory; std::unique_ptr<web_request_factory> m_web_request_factory;
std::unique_ptr<transport_factory> m_transport_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); 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); 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) if ((level & m_trace_level) != trace_level::none)
{ {
utility::ostringstream_t os; try
os << utility::datetime::utc_now().to_string(utility::datetime::date_format::ISO_8601) << _XPLATSTR(" ") << entry << std::endl; {
m_log_writer->write(os.str()); 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 "stdafx.h"
#include "transport_factory.h" #include "transport_factory.h"
#include "websocket_transport.h" #include "websocket_transport.h"
#include "connection_impl.h"
namespace signalr 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) 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"); throw std::exception("not implemented");

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

@ -9,10 +9,12 @@
namespace signalr namespace signalr
{ {
class connection_impl;
class transport_factory class transport_factory
{ {
public: 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(); virtual ~transport_factory();
}; };

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

@ -3,16 +3,17 @@
#include "stdafx.h" #include "stdafx.h"
#include "websocket_transport.h" #include "websocket_transport.h"
#include "logger.h"
namespace signalr namespace signalr
{ {
namespace 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) websocket_transport::websocket_transport(std::shared_ptr<websocket_client> websocket_client, std::shared_ptr<connection_impl> connection)
: m_websocket_client(websocket_client) : m_websocket_client(websocket_client), m_connection(connection)
{ {
// we use this cts to check if the receive loop is running so it should be // 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 // initially cancelled to indicate that the receive loop is not running
@ -41,21 +42,27 @@ namespace signalr
pplx::cancellation_token_source receive_loop_cts; pplx::cancellation_token_source receive_loop_cts;
std::shared_ptr<websocket_client> websocket_client(m_websocket_client); std::shared_ptr<websocket_client> websocket_client(m_websocket_client);
pplx::task_completion_event<void> connect_tce; pplx::task_completion_event<void> connect_tce;
auto connection = m_connection;
m_websocket_client->connect(url) 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 try
{ {
connect_task.get(); connect_task.get();
receive_loop(websocket_client, receive_loop_cts); receive_loop(websocket_client, connection, receive_loop_cts);
connect_tce.set(); connect_tce.set();
} }
catch (const std::exception &e) 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(); 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(); m_receive_loop_cts.cancel();
auto logger = m_connection->get_logger();
return m_websocket_client->close() return m_websocket_client->close()
.then([](pplx::task<void> close_task) .then([logger](pplx::task<void> close_task)
{ mutable {
try try
{ {
close_task.get(); 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 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() 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 try
{ {
@ -104,29 +120,41 @@ namespace signalr
if (!pplx::is_task_cancellation_requested()) 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; return;
} }
// TODO: log, report error, close websocket (when appropriate) // TODO: report error, close websocket (when appropriate)
catch (const web_sockets::client::websocket_exception&) 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 (...) 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.cancel();
}, cts.get_token()); }, cts.get_token());
} }
} }

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

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

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

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

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

@ -33,6 +33,9 @@
<ClInclude Include="..\..\memory_log_writer.h"> <ClInclude Include="..\..\memory_log_writer.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\test_utils.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="..\..\stdafx.cpp"> <ClCompile Include="..\..\stdafx.cpp">
@ -74,6 +77,9 @@
<ClCompile Include="..\..\memory_log_writer.cpp"> <ClCompile Include="..\..\memory_log_writer.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\test_utils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="packages.config" /> <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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#include "stdafx.h" #include "stdafx.h"
#include "test_utils.h"
#include "connection_impl.h" #include "connection_impl.h"
#include "signalrclient\trace_level.h" #include "signalrclient\trace_level.h"
#include "signalrclient\trace_log_writer.h" #include "signalrclient\trace_log_writer.h"
@ -13,14 +14,17 @@ using namespace signalr;
TEST(connection_impl_connection_state, initial_connection_state_is_disconnected) 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()); ASSERT_EQ(connection_state::disconnected, connection->get_connection_state());
} }
TEST(connection_impl_start, cannot_start_non_disconnected_exception) 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(); connection->start().wait();
try try
@ -32,14 +36,31 @@ TEST(connection_impl_start, cannot_start_non_disconnected_exception)
{ {
ASSERT_EQ( ASSERT_EQ(
_XPLATSTR("cannot start a connection that is not in the disconnected state"), _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) 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(); connection->start().wait();
ASSERT_EQ(connection->get_connection_state(), connection_state::connecting); 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( ASSERT_EQ(
_XPLATSTR("web exception: 404 ") + response_phrase, _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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#include "stdafx.h" #include "stdafx.h"
#include "test_utils.h"
#include <cpprest\asyncrt_utils.h> #include <cpprest\asyncrt_utils.h>
#include "signalrclient\trace_log_writer.h" #include "signalrclient\trace_log_writer.h"
#include "logger.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(); 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) 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); logger l(writer, trace_level::all);
l.log(trace_level::messages, _XPLATSTR("message")); 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); 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(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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#include "stdafx.h" #include "stdafx.h"
#include "test_utils.h"
#include "signalrclient\trace_log_writer.h"
#include "test_websocket_client.h" #include "test_websocket_client.h"
#include "websocket_transport.h" #include "websocket_transport.h"
#include "memory_log_writer.h"
using namespace signalr; using namespace signalr;
using namespace web::experimental; 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("")); 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(); 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) TEST(websocket_transport_connect, connect_propagates_exceptions)
{ {
auto client = std::make_shared<test_websocket_client>(); 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 try
{ {
ws_transport.connect(_XPLATSTR("http://fakeuri.org")).wait(); ws_transport.connect(_XPLATSTR("http://fakeuri.org")).get();
ASSERT_TRUE(false); // exception not thrown ASSERT_TRUE(false); // exception not thrown
} }
catch (const std::exception &e) 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) TEST(websocket_transport_connect, cannot_call_connect_on_already_connected_transport)
{ {
auto client = std::make_shared<test_websocket_client>(); 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(); 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) 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>(); 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.connect(_XPLATSTR("http://fakeuri.org")).get();
ws_transport.disconnect().get(); ws_transport.disconnect().get();
@ -97,7 +135,8 @@ TEST(websocket_transport_send, send_creates_and_sends_websocket_messages)
return pplx::task_from_result(); 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(); ws_transport.send(_XPLATSTR("ABC")).wait();
@ -116,7 +155,8 @@ TEST(websocket_transport_disconnect, disconnect_closes_websocket)
return pplx::task_from_result(); 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(); ws_transport.disconnect().get();
@ -132,10 +172,42 @@ TEST(websocket_transport_disconnect, disconnect_does_not_throw)
return pplx::task_from_exception<void>(std::exception()); 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(); 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) TEST(websocket_transport_disconnect, receive_not_called_after_disconnect)
{ {
auto client = std::make_shared<test_websocket_client>(); 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); 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.connect(_XPLATSTR("http://fakeuri.org")).get();
ws_transport.disconnect().get(); ws_transport.disconnect().get();
@ -167,4 +240,63 @@ TEST(websocket_transport_disconnect, receive_not_called_after_disconnect)
ws_transport.disconnect().get(); ws_transport.disconnect().get();
ASSERT_EQ(2, num_called); 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);
} }