From 778435cf02005fa5b2280eb037db3f7e3befef29 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sat, 4 Jul 2020 00:10:16 +0200 Subject: [PATCH] added unit tests for server --- test/webfuse/integration/file.cc | 2 +- test/webfuse/mocks/getattr_matcher.hpp | 36 ++++ test/webfuse/mocks/open_matcher.hpp | 36 ++++ test/webfuse/mocks/readdir_matcher.hpp | 36 ++++ test/webfuse/test_server.cc | 116 ++++++++++++- test/webfuse/test_util/ws_client.cc | 220 ++++++++++++++++++------- test/webfuse/test_util/ws_client.hpp | 7 +- test/webfuse/test_util/ws_server.cc | 7 +- 8 files changed, 384 insertions(+), 76 deletions(-) create mode 100644 test/webfuse/mocks/getattr_matcher.hpp create mode 100644 test/webfuse/mocks/open_matcher.hpp create mode 100644 test/webfuse/mocks/readdir_matcher.hpp diff --git a/test/webfuse/integration/file.cc b/test/webfuse/integration/file.cc index b739e4d..94d5608 100644 --- a/test/webfuse/integration/file.cc +++ b/test/webfuse/integration/file.cc @@ -77,7 +77,7 @@ bool File::hasSubdirectory(std::string const & subdir) bool File::hasContents(std::string const & contents) { std::stringstream command; - command << "./fs_check -c has_contents -f " << path_ << " -a " << contents; + command << "./fs_check -c has_contents -f " << path_ << " -a \'" << contents << "\'"; return invoke(command.str()); } diff --git a/test/webfuse/mocks/getattr_matcher.hpp b/test/webfuse/mocks/getattr_matcher.hpp new file mode 100644 index 0000000..71094c7 --- /dev/null +++ b/test/webfuse/mocks/getattr_matcher.hpp @@ -0,0 +1,36 @@ +#ifndef WF_GETATTR_MATCHER_HPP +#define WF_GETATTR_MATCHER_HPP + +#include +#include + +namespace webfuse_test +{ + +MATCHER_P(GetAttr, inode, "") +{ + if (!json_is_array(arg)) + { + *result_listener << "json array expected"; + return false; + } + + json_t * inode_ = json_array_get(arg, 1); + if (!json_is_integer(inode_)) + { + *result_listener << "inode is expectoed to an integer"; + return false; + } + + if (inode != json_integer_value(inode_)) + { + *result_listener << "inode mismatch: expected" << inode + << " but was " << json_integer_value(inode_); + } + + return true; +} + +} + +#endif diff --git a/test/webfuse/mocks/open_matcher.hpp b/test/webfuse/mocks/open_matcher.hpp new file mode 100644 index 0000000..462792e --- /dev/null +++ b/test/webfuse/mocks/open_matcher.hpp @@ -0,0 +1,36 @@ +#ifndef WF_OPEN_MATCHER_HPP +#define WF_OPEN_MATCHER_HPP + +#include +#include + +namespace webfuse_test +{ + +MATCHER_P(Open, inode, "") +{ + if (!json_is_array(arg)) + { + *result_listener << "json array expected"; + return false; + } + + json_t * inode_ = json_array_get(arg, 1); + if (!json_is_integer(inode_)) + { + *result_listener << "inode is expectoed to an integer"; + return false; + } + + if (inode != json_integer_value(inode_)) + { + *result_listener << "inode mismatch: expected" << inode + << " but was " << json_integer_value(inode_); + } + + return true; +} + +} + +#endif diff --git a/test/webfuse/mocks/readdir_matcher.hpp b/test/webfuse/mocks/readdir_matcher.hpp new file mode 100644 index 0000000..ad2c2ab --- /dev/null +++ b/test/webfuse/mocks/readdir_matcher.hpp @@ -0,0 +1,36 @@ +#ifndef WF_READDIR_MATCHER_HPP +#define WF_READDIR_MATCHER_HPP + +#include +#include + +namespace webfuse_test +{ + +MATCHER_P(ReadDir, inode, "") +{ + if (!json_is_array(arg)) + { + *result_listener << "json array expected"; + return false; + } + + json_t * inode_ = json_array_get(arg, 1); + if (!json_is_integer(inode_)) + { + *result_listener << "inode is expectoed to an integer"; + return false; + } + + if (inode != json_integer_value(inode_)) + { + *result_listener << "inode mismatch: expected" << inode + << " but was " << json_integer_value(inode_); + } + + return true; +} + +} + +#endif diff --git a/test/webfuse/test_server.cc b/test/webfuse/test_server.cc index 2fa5934..e3e5de8 100644 --- a/test/webfuse/test_server.cc +++ b/test/webfuse/test_server.cc @@ -1,5 +1,7 @@ #include +#include + #include #include #include @@ -10,12 +12,26 @@ #include "webfuse/server_config.h" #include "webfuse/test_util/server.hpp" #include "webfuse/test_util/ws_client.hpp" +#include "webfuse/integration/file.hpp" #include "webfuse/mocks/mock_invokation_handler.hpp" #include "webfuse/protocol_names.h" +#include "webfuse/mocks/open_matcher.hpp" +#include "webfuse/mocks/getattr_matcher.hpp" +#include "webfuse/mocks/lookup_matcher.hpp" +#include "webfuse/mocks/readdir_matcher.hpp" using webfuse_test::MockInvokationHander; using webfuse_test::WsClient; using webfuse_test::Server; +using webfuse_test::File; +using webfuse_test::GetAttr; +using webfuse_test::Open; +using webfuse_test::Lookup; +using webfuse_test::ReadDir; +using testing::StrEq; +using testing::_; +using testing::AnyNumber; +using testing::Return; #define TIMEOUT (std::chrono::seconds(10)) @@ -56,10 +72,102 @@ TEST(server, connect) WsClient client(handler, WF_PROTOCOL_NAME_PROVIDER_CLIENT); auto connected = client.Connect(server.GetPort(), WF_PROTOCOL_NAME_ADAPTER_SERVER); - ASSERT_EQ(std::future_status::ready, connected.wait_for(TIMEOUT)); - ASSERT_TRUE(connected.get()); + ASSERT_TRUE(connected); auto disconnected = client.Disconnect(); - ASSERT_EQ(std::future_status::ready, disconnected.wait_for(TIMEOUT)); - ASSERT_TRUE(disconnected.get()); + ASSERT_TRUE(disconnected); +} + +TEST(server, add_filesystem) +{ + Server server; + MockInvokationHander handler; + EXPECT_CALL(handler, Invoke(StrEq("lookup"), _)).Times(AnyNumber()); + EXPECT_CALL(handler, Invoke(StrEq("getattr"), GetAttr(1))).Times(1) + .WillOnce(Return("{\"mode\": 420, \"type\": \"dir\"}")); + WsClient client(handler, WF_PROTOCOL_NAME_PROVIDER_CLIENT); + + auto connected = client.Connect(server.GetPort(), WF_PROTOCOL_NAME_ADAPTER_SERVER); + ASSERT_TRUE(connected); + + std::string response_text = client.Invoke("{\"method\": \"add_filesystem\", \"params\": [\"test\"], \"id\": 42}"); + json_t * response = json_loads(response_text.c_str(), 0, nullptr); + ASSERT_TRUE(json_is_object(response)); + json_t * result = json_object_get(response, "result"); + ASSERT_TRUE(json_is_object(result)); + json_t * id = json_object_get(response, "id"); + ASSERT_EQ(42, json_integer_value(id)); + json_decref(response); + + std::string base_dir = server.GetBaseDir(); + File file(base_dir + "/test"); + ASSERT_TRUE(file.isDirectory()); + + auto disconnected = client.Disconnect(); + ASSERT_TRUE(disconnected); +} + +TEST(server, read) +{ + Server server; + MockInvokationHander handler; + EXPECT_CALL(handler, Invoke(StrEq("lookup"), _)).Times(AnyNumber()); + EXPECT_CALL(handler, Invoke(StrEq("lookup"), Lookup(1, "a.file"))).Times(1) + .WillOnce(Return("{\"inode\": 2, \"mode\": 420, \"type\": \"file\", \"size\": 1}")); + EXPECT_CALL(handler, Invoke(StrEq("open"), Open(2))).Times(1) + .WillOnce(Return("{\"handle\": 42}")); + EXPECT_CALL(handler, Invoke(StrEq("read"), _)).Times(1) + .WillOnce(Return("{\"data\": \"*\", \"format\": \"identity\", \"count\": 1}")); + EXPECT_CALL(handler, Invoke(StrEq("close"), _)).Times(1); + WsClient client(handler, WF_PROTOCOL_NAME_PROVIDER_CLIENT); + + auto connected = client.Connect(server.GetPort(), WF_PROTOCOL_NAME_ADAPTER_SERVER); + ASSERT_TRUE(connected); + + std::string response_text = client.Invoke("{\"method\": \"add_filesystem\", \"params\": [\"test\"], \"id\": 42}"); + json_t * response = json_loads(response_text.c_str(), 0, nullptr); + ASSERT_TRUE(json_is_object(response)); + json_t * result = json_object_get(response, "result"); + ASSERT_TRUE(json_is_object(result)); + json_t * id = json_object_get(response, "id"); + ASSERT_EQ(42, json_integer_value(id)); + json_decref(response); + + std::string base_dir = server.GetBaseDir(); + File file(base_dir + "/test/a.file"); + ASSERT_TRUE(file.hasContents("*")); + + auto disconnected = client.Disconnect(); + ASSERT_TRUE(disconnected); +} + +TEST(server, readdir) +{ + Server server; + MockInvokationHander handler; + EXPECT_CALL(handler, Invoke(StrEq("lookup"), _)).Times(AnyNumber()); + EXPECT_CALL(handler, Invoke(StrEq("getattr"), GetAttr(1))).Times(1) + .WillOnce(Return("{\"mode\": 420, \"type\": \"dir\"}")); + EXPECT_CALL(handler, Invoke(StrEq("readdir"), ReadDir(1))).Times(1) + .WillOnce(Return("[{\"name\": \"foo\", \"inode\": 23}]")); + WsClient client(handler, WF_PROTOCOL_NAME_PROVIDER_CLIENT); + + auto connected = client.Connect(server.GetPort(), WF_PROTOCOL_NAME_ADAPTER_SERVER); + ASSERT_TRUE(connected); + + std::string response_text = client.Invoke("{\"method\": \"add_filesystem\", \"params\": [\"test\"], \"id\": 42}"); + json_t * response = json_loads(response_text.c_str(), 0, nullptr); + ASSERT_TRUE(json_is_object(response)); + json_t * result = json_object_get(response, "result"); + ASSERT_TRUE(json_is_object(result)); + json_t * id = json_object_get(response, "id"); + ASSERT_EQ(42, json_integer_value(id)); + json_decref(response); + + std::string base_dir = server.GetBaseDir(); + File file(base_dir + "/test"); + ASSERT_TRUE(file.hasSubdirectory("foo")); + + auto disconnected = client.Disconnect(); + ASSERT_TRUE(disconnected); } \ No newline at end of file diff --git a/test/webfuse/test_util/ws_client.cc b/test/webfuse/test_util/ws_client.cc index 015fc53..d19a189 100644 --- a/test/webfuse/test_util/ws_client.cc +++ b/test/webfuse/test_util/ws_client.cc @@ -1,12 +1,18 @@ #include "webfuse/test_util/ws_client.hpp" #include "webfuse/test_util/invokation_handler.hpp" +#include "webfuse/status.h" #include +#include #include #include #include #include +#include +#include + +#define TIMEOUT (std::chrono::seconds(10)) namespace { @@ -18,7 +24,7 @@ public: virtual void OnConnectionEstablished(lws * wsi) = 0; virtual void OnConnectionError(lws * wsi) = 0; virtual void OnConnectionClosed(lws * wsi) = 0; - virtual void OnMessageReceived(lws * wsi, void * data, size_t length) = 0; + virtual void OnMessageReceived(lws * wsi, char * data, size_t length) = 0; virtual int OnWritable(lws * wsi) = 0; }; @@ -52,7 +58,7 @@ static int webfuse_test_WsClient_callback( server->OnConnectionClosed(wsi); break; case LWS_CALLBACK_CLIENT_RECEIVE: - server->OnMessageReceived(wsi, in, len); + server->OnMessageReceived(wsi, reinterpret_cast(in), len); break; case LWS_CALLBACK_SERVER_WRITEABLE: // fall-through @@ -81,8 +87,8 @@ public: : wsi_(nullptr) , handler_(handler) , protocol_(protocol) - , promise_connected(nullptr) - , promise_disconnected(nullptr) + , conn_state(connection_state::disconnected) + , await_response(false) , remote_port(0) , remote_use_tls(false) { @@ -122,66 +128,75 @@ public: lws_context_destroy(context); } - std::future Connect(int port, std::string const & protocol, bool use_tls) + bool Connect(int port, std::string const & protocol, bool use_tls) { - std::future result; - { - std::unique_lock lock(mutex); - remote_port = port; - remote_protocol = protocol; - remote_use_tls = use_tls; - commands.push(command::connect); - promise_connected = new std::promise; - result = promise_connected->get_future(); - } - lws_cancel_service(context); + std::unique_lock lock(mutex); + remote_port = port; + remote_protocol = protocol; + remote_use_tls = use_tls; + conn_state = connection_state::connecting; + commands.push(command::connect); - return result; + lock.unlock(); + lws_cancel_service(context); + lock.lock(); + + convar.wait_for(lock, TIMEOUT, [&]() { + return (conn_state != connection_state::connecting); + }); + + return (connection_state::connected == conn_state); } - std::future Disconnect() + bool Disconnect() { - std::future result; - { - std::unique_lock lock(mutex); - commands.push(command::disconnect); - promise_disconnected = new std::promise; - result = promise_disconnected->get_future(); - } + std::unique_lock lock(mutex); + commands.push(command::disconnect); + conn_state = connection_state::disconnecting; + + lock.unlock(); lws_cancel_service(context); + lock.lock(); - return result; + convar.wait_for(lock, TIMEOUT, [&]() { + return (conn_state != connection_state::disconnecting); + }); + return (connection_state::disconnected == conn_state); } - std::future Invoke(std::string const & message) + std::string Invoke(std::string const & message) { - std::promise p; - p.set_exception(std::make_exception_ptr(std::runtime_error("not implemented"))); - return p.get_future(); + std::unique_lock lock(mutex); + send_queue.push(message); + commands.push(command::send); + await_response = true; + + lock.unlock(); + lws_cancel_service(context); + lock.lock(); + + convar.wait_for(lock, TIMEOUT, [&]() { + return !await_response; + }); + + return response; } void OnConnectionEstablished(lws * wsi) { std::unique_lock lock(mutex); wsi_ = wsi; - if (nullptr != promise_connected) - { - promise_connected->set_value(true); - delete promise_connected; - promise_connected = nullptr; - } + conn_state = connection_state::connected; + convar.notify_all(); } void OnConnectionError(lws * wsi) { std::unique_lock lock(mutex); - if (nullptr != promise_connected) - { - promise_connected->set_value(false); - delete promise_connected; - promise_connected = nullptr; - } + wsi_ = nullptr; + conn_state = connection_state::disconnected; + convar.notify_all(); } void OnConnectionClosed(lws * wsi) @@ -190,33 +205,97 @@ public: if (wsi == wsi_) { wsi_ = nullptr; - if (nullptr != promise_connected) - { - promise_connected->set_value(false); - delete promise_connected; - promise_connected = nullptr; - } - if (nullptr != promise_disconnected) - { - promise_disconnected->set_value(true); - delete promise_disconnected; - promise_disconnected = nullptr; - } + conn_state = connection_state::disconnected; + convar.notify_all(); } } - void OnMessageReceived(lws * wsi, void * data, size_t length) + void OnMessageReceived(lws * wsi, char * data, size_t length) { + std::unique_lock lock(mutex); + if (await_response) + { + response = std::string(data, length); + await_response = false; + convar.notify_all(); + } + else + { + lock.unlock(); + json_t * request = json_loadb(data, length, 0, nullptr); + if (nullptr != request) + { + json_t * method = json_object_get(request, "method"); + json_t * params = json_object_get(request, "params"); + json_t * id = json_object_get(request, "id"); + + json_t * response = json_object(); + try + { + std::string result_text = handler_.Invoke(json_string_value(method), params); + json_t * result = json_loads(result_text.c_str(), 0, nullptr); + if (nullptr != result) + { + json_object_set_new(response, "result", result); + } + else + { + json_t * error = json_object(); + json_object_set_new(error, "code", json_integer(WF_BAD)); + json_object_set_new(response, "error",error); + } + + } + catch (...) + { + json_t * error = json_object(); + json_object_set_new(error, "code", json_integer(WF_BAD)); + json_object_set_new(response, "error",error); + } + + json_object_set(response, "id", id); + + char * response_text = json_dumps(response, 0); + lock.lock(); + send_queue.push(response_text); + commands.push(command::send); + lock.unlock(); + + lws_cancel_service(context); + + free(response_text); + + json_decref(response); + json_decref(request); + } + } } int OnWritable(lws * wsi) { std::unique_lock lock(mutex); - if (nullptr != promise_disconnected) + if (conn_state == connection_state::disconnecting) { return 1; } + else if (!send_queue.empty()) + { + std::string message = send_queue.front(); + send_queue.pop(); + bool has_more = !send_queue.empty(); + lock.unlock(); + + unsigned char * data = new unsigned char[LWS_PRE + message.size()]; + memcpy(&data[LWS_PRE], message.c_str(), message.size()); + lws_write(wsi, &data[LWS_PRE], message.size(), LWS_WRITE_TEXT); + delete[] data; + + if (has_more) + { + lws_callback_on_writable(wsi); + } + } return 0; } @@ -228,7 +307,16 @@ private: run, shutdown, connect, - disconnect + disconnect, + send + }; + + enum class connection_state + { + disconnected, + connected, + connecting, + disconnecting }; static void Run(Private * self) @@ -270,6 +358,9 @@ private: case command::disconnect: lws_callback_on_writable(self->wsi_); break; + case command::send: + lws_callback_on_writable(self->wsi_); + break; default: break; } @@ -299,19 +390,22 @@ private: lws * wsi_; InvokationHandler & handler_; std::string protocol_; - - std::promise * promise_connected; - std::promise * promise_disconnected; + connection_state conn_state; + bool await_response; + std::string response; lws_context_creation_info info; lws_protocols protocols[2]; lws_context * context; std::thread thread; std::mutex mutex; + std::condition_variable convar; std::queue commands; int remote_port; std::string remote_protocol; bool remote_use_tls; + + std::queue send_queue; }; WsClient::WsClient( @@ -327,17 +421,17 @@ WsClient::~WsClient() delete d; } -std::future WsClient::Connect(int port, std::string const & protocol, bool use_tls) +bool WsClient::Connect(int port, std::string const & protocol, bool use_tls) { return d->Connect(port, protocol, use_tls); } -std::future WsClient::Disconnect() +bool WsClient::Disconnect() { return d->Disconnect(); } -std::future WsClient::Invoke(std::string const & message) +std::string WsClient::Invoke(std::string const & message) { return d->Invoke(message); } diff --git a/test/webfuse/test_util/ws_client.hpp b/test/webfuse/test_util/ws_client.hpp index 8aa14ce..5c04231 100644 --- a/test/webfuse/test_util/ws_client.hpp +++ b/test/webfuse/test_util/ws_client.hpp @@ -2,7 +2,6 @@ #define WF_TEST_UTIL_WS_CLIENT_HPP #include -#include namespace webfuse_test { @@ -18,9 +17,9 @@ public: InvokationHandler& handler, std::string const & protocol); virtual ~WsClient(); - std::future Connect(int port, std::string const & protocol, bool use_tls = true); - std::future Disconnect(); - std::future Invoke(std::string const & message); + bool Connect(int port, std::string const & protocol, bool use_tls = true); + bool Disconnect(); + std::string Invoke(std::string const & message); private: class Private; Private *d; diff --git a/test/webfuse/test_util/ws_server.cc b/test/webfuse/test_util/ws_server.cc index 1d75ea3..d9631be 100644 --- a/test/webfuse/test_util/ws_server.cc +++ b/test/webfuse/test_util/ws_server.cc @@ -180,10 +180,9 @@ WsServer::Private::Private( WsServer::Private::~Private() { - { - std::unique_lock lock(mutex); - is_shutdown_requested = true; - } + std::unique_lock lock(mutex); + is_shutdown_requested = true; + lock.unlock(); lws_cancel_service(ws_context); context.join();