diff --git a/CMakeLists.txt b/CMakeLists.txt index 38a4726..5ce597b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,7 @@ add_library(webfuse-core STATIC lib/webfuse/core/status.c lib/webfuse/core/string.c lib/webfuse/core/path.c + lib/webfuse/core/lws_log.c ) set_target_properties(webfuse-core PROPERTIES OUTPUT_NAME webfuse-core) @@ -291,25 +292,40 @@ pkg_check_modules(GMOCK gmock) add_executable(alltests test/msleep.cc + test/die_if.cc test/mock_authenticator.cc test/mock_request.cc - test/test_container_of.cc - test/test_response_parser.cc - test/test_server.cc - test/test_timepoint.cc - test/test_timer.cc - test/test_url.cc - test/test_credentials.cc - test/test_authenticator.cc - test/test_authenticators.cc - test/test_string.cc - test/test_slist.cc - test/test_path.cc - test/test_static_filesystem.cc + test/core/test_container_of.cc + test/core/test_string.cc + test/core/test_slist.cc + test/core/test_path.cc + test/core/test_status.cc + test/core/test_message.cc + test/core/test_message_queue.cc + test/adapter/test_response_parser.cc + test/adapter/test_server.cc + test/adapter/test_timepoint.cc + test/adapter/test_timer.cc + test/adapter/test_credentials.cc + test/adapter/test_authenticator.cc + test/adapter/test_authenticators.cc + test/adapter/test_fuse_req.cc + test/adapter/jsonrpc/test_util.cc + test/adapter/jsonrpc/test_is_request.cc + test/adapter/jsonrpc/test_request.cc + test/adapter/jsonrpc/test_is_response.cc + test/adapter/jsonrpc/test_response.cc + test/adapter/jsonrpc/test_server.cc + test/adapter/jsonrpc/test_proxy.cc + test/provider/test_url.cc + test/provider/test_static_filesystem.cc + test/integration/test_integration.cc + test/integration/server.cc + test/integration/provider.cc ) target_link_libraries(alltests PUBLIC webfuse-adapter-static webfuse-provider-static webfuse-core ${EXTRA_LIBS} ${GMOCK_LIBRARIES} ${GTEST_LIBRARIES}) -target_include_directories(alltests PUBLIC lib ${GMOCK_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS}) +target_include_directories(alltests PUBLIC test lib ${GMOCK_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS}) target_compile_options(alltests PUBLIC ${GMOCK_CFLAGS} ${GTEST_CFLAGS}) enable_testing() @@ -320,6 +336,7 @@ add_custom_target(coverage COMMAND mkdir -p coverage COMMAND lcov --capture --directory . --output-file coverage/lcov.info COMMAND lcov --remove coverage/lcov.info '/usr/*' --output-file coverage/lcov.info + COMMAND lcov --remove coverage/lcov.info '*/test/*' --output-file coverage/lcov.info ) add_dependencies(coverage alltests) diff --git a/lib/webfuse/adapter/impl/jsonrpc/proxy.c b/lib/webfuse/adapter/impl/jsonrpc/proxy.c index 5435562..ec0da2e 100644 --- a/lib/webfuse/adapter/impl/jsonrpc/proxy.c +++ b/lib/webfuse/adapter/impl/jsonrpc/proxy.c @@ -3,8 +3,6 @@ #include "webfuse/adapter/impl/jsonrpc/response.h" -#define WF_DEFAULT_TIMEOUT (10 * 1000) - static void wf_impl_jsonrpc_proxy_timeout( struct wf_impl_timer * timer) { @@ -53,8 +51,9 @@ static json_t * wf_impl_jsonrpc_request_create( break; default: fprintf(stderr, "fatal: unknown param_type '%c'\n", *param_type); - exit(EXIT_FAILURE); - break; + json_decref(params); + json_decref(request); + return NULL; } } @@ -71,10 +70,12 @@ static json_t * wf_impl_jsonrpc_request_create( void wf_impl_jsonrpc_proxy_init( struct wf_impl_jsonrpc_proxy * proxy, struct wf_impl_timeout_manager * timeout_manager, + int timeout, wf_impl_jsonrpc_send_fn * send, void * user_data) { proxy->send = send; + proxy->timeout = timeout; proxy->user_data = user_data; proxy->request.is_pending = false; @@ -84,13 +85,21 @@ void wf_impl_jsonrpc_proxy_init( void wf_impl_jsonrpc_proxy_cleanup( struct wf_impl_jsonrpc_proxy * proxy) { - wf_impl_timer_cleanup(&proxy->request.timer); - if (proxy->request.is_pending) { - proxy->request.finished(proxy->request.user_data, WF_BAD, NULL); + void * user_data = proxy->request.user_data; + wf_impl_jsonrpc_proxy_finished_fn * finished = proxy->request.finished; + proxy->request.is_pending = false; + proxy->request.finished = NULL; + proxy->request.user_data = NULL; + proxy->request.id = 0; + wf_impl_timer_cancel(&proxy->request.timer); + + finished(user_data, WF_BAD, NULL); } + + wf_impl_timer_cleanup(&proxy->request.timer); } void wf_impl_jsonrpc_proxy_invoke( @@ -108,25 +117,29 @@ void wf_impl_jsonrpc_proxy_invoke( proxy->request.finished = finished; proxy->request.user_data = user_data; proxy->request.id = 42; - wf_impl_timer_start(&proxy->request.timer, wf_impl_timepoint_in_msec(WF_DEFAULT_TIMEOUT), + wf_impl_timer_start(&proxy->request.timer, wf_impl_timepoint_in_msec(proxy->timeout), &wf_impl_jsonrpc_proxy_timeout, proxy); va_list args; va_start(args, param_info); json_t * request = wf_impl_jsonrpc_request_create(method_name, proxy->request.id, param_info, args); va_end(args); + + bool const is_send = ((NULL != request) && (proxy->send(request, proxy->user_data))); + if (!is_send) + { + proxy->request.is_pending = false; + proxy->request.finished = NULL; + proxy->request.user_data = NULL; + proxy->request.id = 0; + wf_impl_timer_cancel(&proxy->request.timer); + + finished(user_data, WF_BAD, NULL); + + } + if (NULL != request) { - if (!proxy->send(request, proxy->user_data)) - { - proxy->request.is_pending = false; - proxy->request.finished = NULL; - proxy->request.user_data = NULL; - proxy->request.id = 0; - wf_impl_timer_cancel(&proxy->request.timer); - - finished(user_data, WF_BAD, NULL); - } json_decref(request); } } diff --git a/lib/webfuse/adapter/impl/jsonrpc/proxy.h b/lib/webfuse/adapter/impl/jsonrpc/proxy.h index 92b4508..0433747 100644 --- a/lib/webfuse/adapter/impl/jsonrpc/proxy.h +++ b/lib/webfuse/adapter/impl/jsonrpc/proxy.h @@ -39,6 +39,7 @@ struct wf_impl_jsonrpc_request struct wf_impl_jsonrpc_proxy { struct wf_impl_jsonrpc_request request; + int timeout; wf_impl_jsonrpc_send_fn * send; void * user_data; }; @@ -46,6 +47,7 @@ struct wf_impl_jsonrpc_proxy extern void wf_impl_jsonrpc_proxy_init( struct wf_impl_jsonrpc_proxy * proxy, struct wf_impl_timeout_manager * manager, + int timeout, wf_impl_jsonrpc_send_fn * send, void * user_data); diff --git a/lib/webfuse/adapter/impl/operation/read.c b/lib/webfuse/adapter/impl/operation/read.c index 4022a99..11eb0f2 100644 --- a/lib/webfuse/adapter/impl/operation/read.c +++ b/lib/webfuse/adapter/impl/operation/read.c @@ -93,7 +93,7 @@ void wf_impl_operation_read( { int const length = (size <= WF_MAX_READ_LENGTH) ? (int) size : WF_MAX_READ_LENGTH; int handle = (file_info->fh & INT_MAX); - wf_impl_jsonrpc_proxy_invoke(rpc, &wf_impl_operation_read_finished, request, "read", "siiii", user_data->name, inode, handle, (int) offset, length); + wf_impl_jsonrpc_proxy_invoke(rpc, &wf_impl_operation_read_finished, request, "read", "siiii", user_data->name, (int) inode, handle, (int) offset, length); } else { diff --git a/lib/webfuse/adapter/impl/server.c b/lib/webfuse/adapter/impl/server.c index 7a3462a..15e18f4 100644 --- a/lib/webfuse/adapter/impl/server.c +++ b/lib/webfuse/adapter/impl/server.c @@ -10,8 +10,8 @@ #include "webfuse/adapter/impl/server_config.h" #include "webfuse/adapter/impl/server_protocol.h" +#include "webfuse/core/lws_log.h" -#define WF_DISABLE_LWS_LOG 0 #define WF_SERVER_PROTOCOL_COUNT 3 struct wf_server @@ -33,7 +33,7 @@ static bool wf_impl_server_tls_enabled( static struct lws_context * wf_impl_server_context_create( struct wf_server * server) { - lws_set_log_level(WF_DISABLE_LWS_LOG, NULL); + wf_lwslog_disable(); memset(server->ws_protocols, 0, sizeof(struct lws_protocols) * WF_SERVER_PROTOCOL_COUNT); server->ws_protocols[0].name = "http"; @@ -125,6 +125,12 @@ void wf_impl_server_dispose( free(server); } +bool wf_impl_server_is_operational( + struct wf_server * server) +{ + return server->protocol.is_operational; +} + void wf_impl_server_service( struct wf_server * server, int timeout_ms) diff --git a/lib/webfuse/adapter/impl/server.h b/lib/webfuse/adapter/impl/server.h index db26e69..1f04cc7 100644 --- a/lib/webfuse/adapter/impl/server.h +++ b/lib/webfuse/adapter/impl/server.h @@ -1,6 +1,10 @@ #ifndef WF_ADAPTER_IMPL_SERVER_H #define WF_ADAPTER_IMPL_SERVER_H +#ifndef __cplusplus +#include +#endif + #ifdef __cplusplus extern "C" { @@ -15,6 +19,9 @@ extern struct wf_server * wf_impl_server_create( extern void wf_impl_server_dispose( struct wf_server * server); +extern bool wf_impl_server_is_operational( + struct wf_server * server); + extern void wf_impl_server_service( struct wf_server * server, int timeout_ms); diff --git a/lib/webfuse/adapter/impl/server_protocol.c b/lib/webfuse/adapter/impl/server_protocol.c index fa5c837..65f6047 100644 --- a/lib/webfuse/adapter/impl/server_protocol.c +++ b/lib/webfuse/adapter/impl/server_protocol.c @@ -30,6 +30,9 @@ static int wf_impl_server_protocol_callback( switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + protocol->is_operational = true; + break; case LWS_CALLBACK_ESTABLISHED: session = wf_impl_session_manager_add( &protocol->session_manager, @@ -206,6 +209,7 @@ void wf_impl_server_protocol_init( char * mount_point) { protocol->mount_point = strdup(mount_point); + protocol->is_operational = false; wf_impl_timeout_manager_init(&protocol->timeout_manager); wf_impl_session_manager_init(&protocol->session_manager); @@ -220,6 +224,8 @@ void wf_impl_server_protocol_cleanup( struct wf_server_protocol * protocol) { free(protocol->mount_point); + protocol->is_operational = false; + wf_impl_jsonrpc_server_cleanup(&protocol->server); wf_impl_timeout_manager_cleanup(&protocol->timeout_manager); wf_impl_authenticators_cleanup(&protocol->authenticators); diff --git a/lib/webfuse/adapter/impl/server_protocol.h b/lib/webfuse/adapter/impl/server_protocol.h index 2ae0fe6..8e4cd8d 100644 --- a/lib/webfuse/adapter/impl/server_protocol.h +++ b/lib/webfuse/adapter/impl/server_protocol.h @@ -7,6 +7,10 @@ #include "webfuse/adapter/impl/session_manager.h" #include "webfuse/adapter/impl/jsonrpc/server.h" +#ifndef __cplusplus +#include +#endif + #ifdef __cplusplus extern "C" { @@ -21,6 +25,7 @@ struct wf_server_protocol struct wf_impl_authenticators authenticators; struct wf_impl_session_manager session_manager; struct wf_impl_jsonrpc_server server; + bool is_operational; }; extern void wf_impl_server_protocol_init( diff --git a/lib/webfuse/adapter/impl/session.c b/lib/webfuse/adapter/impl/session.c index d4f1f56..82f9b80 100644 --- a/lib/webfuse/adapter/impl/session.c +++ b/lib/webfuse/adapter/impl/session.c @@ -13,6 +13,8 @@ #include #include +#define WF_DEFAULT_TIMEOUT (10 * 1000) + static bool wf_impl_session_send( json_t * request, void * user_data) @@ -55,7 +57,7 @@ struct wf_impl_session * wf_impl_session_create( session->is_authenticated = false; session->authenticators = authenticators; session->server = server; - wf_impl_jsonrpc_proxy_init(&session->rpc, timeout_manager, &wf_impl_session_send, session); + wf_impl_jsonrpc_proxy_init(&session->rpc, timeout_manager, WF_DEFAULT_TIMEOUT, &wf_impl_session_send, session); wf_slist_init(&session->messages); } @@ -79,10 +81,10 @@ static void wf_impl_session_dispose_filesystems( void wf_impl_session_dispose( struct wf_impl_session * session) { - wf_impl_session_dispose_filesystems(&session->filesystems); - wf_impl_jsonrpc_proxy_cleanup(&session->rpc); wf_message_queue_cleanup(&session->messages); + + wf_impl_session_dispose_filesystems(&session->filesystems); session->is_authenticated = false; session->wsi = NULL; session->authenticators = NULL; diff --git a/lib/webfuse/core/lws_log.c b/lib/webfuse/core/lws_log.c new file mode 100644 index 0000000..506e530 --- /dev/null +++ b/lib/webfuse/core/lws_log.c @@ -0,0 +1,18 @@ +#include "webfuse/core/lws_log.h" +#include +#include + +#define WF_LWSLOG_DISABLE 0 + +static bool wf_lwslog_is_diabled = false; + +void wf_lwslog_disable(void) +{ + if (!wf_lwslog_is_diabled) + { + lws_set_log_level(WF_LWSLOG_DISABLE, NULL); + wf_lwslog_is_diabled = true; + } +} + + diff --git a/lib/webfuse/core/lws_log.h b/lib/webfuse/core/lws_log.h new file mode 100644 index 0000000..6bbbdda --- /dev/null +++ b/lib/webfuse/core/lws_log.h @@ -0,0 +1,15 @@ +#ifndef WF_LWS_LOG_H +#define WF_LWS_LOG_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern void wf_lwslog_disable(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/webfuse/core/slist.c b/lib/webfuse/core/slist.c index 5c9dfb1..c6896d7 100644 --- a/lib/webfuse/core/slist.c +++ b/lib/webfuse/core/slist.c @@ -27,11 +27,6 @@ void wf_slist_append( item->next = NULL; list->last->next = item; list->last = item; - - if (NULL == list->head.next) - { - list->head.next = item; - } } struct wf_slist_item * wf_slist_remove_first( diff --git a/lib/webfuse/core/status.c b/lib/webfuse/core/status.c index 9eb9964..d5bc7ba 100644 --- a/lib/webfuse/core/status.c +++ b/lib/webfuse/core/status.c @@ -23,7 +23,7 @@ char const * wf_status_tostring(wf_status status) { case WF_GOOD: return "Good"; case WF_BAD: return "Bad"; - case WF_BAD_NOTIMPLEMENTED: return "Bad (not implelemted)"; + case WF_BAD_NOTIMPLEMENTED: return "Bad (not implemented)"; case WF_BAD_TIMEOUT: return "Bad (timeout)"; case WF_BAD_BUSY: return "Bad (busy)"; case WF_BAD_FORMAT: return "Bad (format)"; diff --git a/lib/webfuse/provider/impl/client.c b/lib/webfuse/provider/impl/client.c index 3a503e0..b88207a 100644 --- a/lib/webfuse/provider/impl/client.c +++ b/lib/webfuse/provider/impl/client.c @@ -10,9 +10,9 @@ #include "webfuse/provider/impl/client_protocol.h" #include "webfuse/provider/impl/client_config.h" #include "webfuse/provider/impl/url.h" +#include "webfuse/core/lws_log.h" #define WFP_PROTOCOL ("fs") -#define WFP_DISABLE_LWS_LOG 0 #define WFP_CLIENT_PROTOCOL_COUNT 2 struct wfp_client @@ -29,7 +29,7 @@ struct wfp_client struct wfp_client * wfp_impl_client_create( struct wfp_client_config * config) { - lws_set_log_level(WFP_DISABLE_LWS_LOG, NULL); + wf_lwslog_disable(); struct wfp_client * client = malloc(sizeof(struct wfp_client)); if (NULL != client) @@ -99,6 +99,12 @@ void wfp_impl_client_disconnect( // ToDo: implement me } +bool wfp_impl_client_is_connected( + struct wfp_client * client) +{ + return client->protocol.is_connected; +} + void wfp_impl_client_service( struct wfp_client * client, int timeout_ms) diff --git a/lib/webfuse/provider/impl/client.h b/lib/webfuse/provider/impl/client.h index 01e6d73..4ade5f2 100644 --- a/lib/webfuse/provider/impl/client.h +++ b/lib/webfuse/provider/impl/client.h @@ -1,6 +1,10 @@ #ifndef WF_PROVIDER_IMPL_CLIENT_H #define WF_PROVIDER_IMPL_CLIENT_H +#ifndef __cplusplus +#include +#endif + #ifdef __cplusplus extern "C" { @@ -27,13 +31,12 @@ extern void wfp_impl_client_connect( extern void wfp_impl_client_disconnect( struct wfp_client * client); -extern void wfp_impl_client_settimeout( - struct wfp_client * client, - unsigned int timepoint); - extern void wfp_impl_client_dispose( struct wfp_client * client); +extern bool wfp_impl_client_is_connected( + struct wfp_client * client); + extern void wfp_impl_client_service( struct wfp_client * client, int timeout_ms); diff --git a/lib/webfuse/provider/impl/client_protocol.c b/lib/webfuse/provider/impl/client_protocol.c index 788c314..cf31a98 100644 --- a/lib/webfuse/provider/impl/client_protocol.c +++ b/lib/webfuse/provider/impl/client_protocol.c @@ -35,6 +35,15 @@ static void wfp_impl_client_protocol_process_request( json_t * request = json_loadb(message, length, 0, NULL); if (NULL != request) { + // FIXME: is_connected should be invoked, when filesystem added + if ((!protocol->is_connected) && (NULL != json_object_get(request, "result"))) + { + protocol->is_connected = true; + protocol->provider.connected(protocol->user_data); + } + + + struct wfp_impl_invokation_context context = { .provider = &protocol->provider, @@ -84,13 +93,15 @@ static int wfp_impl_client_protocol_callback( { case LWS_CALLBACK_CLIENT_ESTABLISHED: wfp_impl_client_protocol_add_filesystem(protocol); - protocol->provider.connected(protocol->user_data); + // Defer is_connected until response received break; case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + protocol->is_connected = false; protocol->provider.disconnected(protocol->user_data); break; case LWS_CALLBACK_CLIENT_CLOSED: - protocol->provider.connected(protocol->user_data); + protocol->is_connected = false; + protocol->provider.disconnected(protocol->user_data); break; case LWS_CALLBACK_CLIENT_RECEIVE: wfp_impl_client_protocol_process_request(protocol, in, len); @@ -126,6 +137,7 @@ void wfp_impl_client_protocol_init( struct wfp_provider const * provider, void * user_data) { + protocol->is_connected = false; wf_slist_init(&protocol->messages); protocol->wsi = NULL; diff --git a/lib/webfuse/provider/impl/client_protocol.h b/lib/webfuse/provider/impl/client_protocol.h index 54929c3..9087d94 100644 --- a/lib/webfuse/provider/impl/client_protocol.h +++ b/lib/webfuse/provider/impl/client_protocol.h @@ -16,6 +16,7 @@ struct lws_protocols; struct wfp_client_protocol { + bool is_connected; struct wfp_request request; struct wfp_provider provider; void * user_data; diff --git a/test/adapter/jsonrpc/test_is_request.cc b/test/adapter/jsonrpc/test_is_request.cc new file mode 100644 index 0000000..5fef81e --- /dev/null +++ b/test/adapter/jsonrpc/test_is_request.cc @@ -0,0 +1,112 @@ +#include +#include "webfuse/adapter/impl/jsonrpc/request.h" + +TEST(jsonrpc_is_request, request_with_object_params) +{ + json_t * request = json_object(); + json_object_set_new(request, "method", json_string("method")); + json_object_set_new(request, "params", json_object()); + json_object_set_new(request, "id", json_integer(42)); + + ASSERT_TRUE(wf_impl_jsonrpc_is_request(request)); + + json_decref(request); +} + +TEST(jsonrpc_is_request, request_with_array_params) +{ + json_t * request = json_object(); + json_object_set_new(request, "method", json_string("method")); + json_object_set_new(request, "params", json_array()); + json_object_set_new(request, "id", json_integer(42)); + + ASSERT_TRUE(wf_impl_jsonrpc_is_request(request)); + + json_decref(request); +} + +TEST(jsonrpc_is_request, null_request) +{ + ASSERT_FALSE(wf_impl_jsonrpc_is_request(nullptr)); +} + +TEST(jsonrpc_is_request, invalid_request) +{ + json_t * request = json_array(); + json_array_append_new(request, json_string("method")); + json_array_append_new(request, json_object()); + json_array_append_new(request, json_integer(42)); + + ASSERT_FALSE(wf_impl_jsonrpc_is_request(request)); + + json_decref(request); +} + +TEST(jsonrpc_is_request, invalid_request_without_id) +{ + json_t * request = json_object(); + json_object_set_new(request, "method", json_string("method")); + json_object_set_new(request, "params", json_object()); + + ASSERT_FALSE(wf_impl_jsonrpc_is_request(request)); + + json_decref(request); +} + +TEST(jsonrpc_is_request, invalid_request_due_to_invalid_id) +{ + json_t * request = json_object(); + json_object_set_new(request, "method", json_string("method")); + json_object_set_new(request, "params", json_object()); + json_object_set_new(request, "id", json_string("42")); + + ASSERT_FALSE(wf_impl_jsonrpc_is_request(request)); + + json_decref(request); +} + +TEST(jsonrpc_is_request, invalid_request_without_method) +{ + json_t * request = json_object(); + json_object_set_new(request, "params", json_object()); + json_object_set_new(request, "id", json_integer(42)); + + ASSERT_FALSE(wf_impl_jsonrpc_is_request(request)); + + json_decref(request); +} + +TEST(jsonrpc_is_request, invalid_request_due_to_invalid_method) +{ + json_t * request = json_object(); + json_object_set_new(request, "method", json_integer(42)); + json_object_set_new(request, "params", json_object()); + json_object_set_new(request, "id", json_integer(42)); + + ASSERT_FALSE(wf_impl_jsonrpc_is_request(request)); + + json_decref(request); +} + +TEST(jsonrpc_is_request, invalid_request_without_params) +{ + json_t * request = json_object(); + json_object_set_new(request, "method", json_string("method")); + json_object_set_new(request, "id", json_integer(42)); + + ASSERT_FALSE(wf_impl_jsonrpc_is_request(request)); + + json_decref(request); +} + +TEST(jsonrpc_is_request, invalid_request_due_to_invalid_params) +{ + json_t * request = json_object(); + json_object_set_new(request, "methdo", json_string("method")); + json_object_set_new(request, "params", json_string("params")); + json_object_set_new(request, "id", json_integer(42)); + + ASSERT_FALSE(wf_impl_jsonrpc_is_request(request)); + + json_decref(request); +} diff --git a/test/adapter/jsonrpc/test_is_response.cc b/test/adapter/jsonrpc/test_is_response.cc new file mode 100644 index 0000000..6182cd0 --- /dev/null +++ b/test/adapter/jsonrpc/test_is_response.cc @@ -0,0 +1,94 @@ +#include +#include "webfuse/adapter/impl/jsonrpc/response.h" + +TEST(jsonrpc_is_response, valid_result) +{ + json_t * message = json_object(); + json_object_set_new(message, "result", json_object()); + json_object_set_new(message, "id", json_integer(42)); + + ASSERT_TRUE(wf_impl_jsonrpc_is_response(message)); + + json_decref(message); +} + +TEST(jsonrpc_is_response, valid_result_string) +{ + json_t * message = json_object(); + json_object_set_new(message, "result", json_string("also valid")); + json_object_set_new(message, "id", json_integer(42)); + + ASSERT_TRUE(wf_impl_jsonrpc_is_response(message)); + + json_decref(message); +} + +TEST(jsonrpc_is_response, valid_error) +{ + json_t * message = json_object(); + json_object_set_new(message, "error", json_object()); + json_object_set_new(message, "id", json_integer(42)); + + ASSERT_TRUE(wf_impl_jsonrpc_is_response(message)); + + json_decref(message); +} + +TEST(jsonrpc_is_response, invalid_null) +{ + ASSERT_FALSE(wf_impl_jsonrpc_is_response(nullptr)); +} + +TEST(jsonrpc_is_response, invalid_message) +{ + json_t * message = json_array(); + json_array_append_new(message, json_object()); + json_array_append_new(message, json_integer(42)); + + ASSERT_FALSE(wf_impl_jsonrpc_is_response(message)); + + json_decref(message); +} + +TEST(jsonrpc_is_response, invalid_missing_id) +{ + json_t * message = json_object(); + json_object_set_new(message, "result", json_object()); + + ASSERT_FALSE(wf_impl_jsonrpc_is_response(message)); + + json_decref(message); +} + +TEST(jsonrpc_is_response, invalid_id_wrong_type) +{ + json_t * message = json_object(); + json_object_set_new(message, "result", json_object()); + json_object_set_new(message, "id", json_string("42")); + + ASSERT_FALSE(wf_impl_jsonrpc_is_response(message)); + + json_decref(message); +} + + +TEST(jsonrpc_is_response, invalid_missing_result_and_error) +{ + json_t * message = json_object(); + json_object_set_new(message, "id", json_integer(42)); + + ASSERT_FALSE(wf_impl_jsonrpc_is_response(message)); + + json_decref(message); +} + +TEST(jsonrpc_is_response, invalid_error_wrong_type) +{ + json_t * message = json_object(); + json_object_set_new(message, "error", json_array()); + json_object_set_new(message, "id", json_integer(42)); + + ASSERT_FALSE(wf_impl_jsonrpc_is_response(message)); + + json_decref(message); +} diff --git a/test/adapter/jsonrpc/test_proxy.cc b/test/adapter/jsonrpc/test_proxy.cc new file mode 100644 index 0000000..1355c4e --- /dev/null +++ b/test/adapter/jsonrpc/test_proxy.cc @@ -0,0 +1,393 @@ +#include +#include "webfuse/adapter/impl/jsonrpc/proxy.h" +#include "webfuse/adapter/impl/time/timeout_manager.h" +#include "msleep.hpp" + +using webfuse_test::msleep; + +#define WF_DEFAULT_TIMEOUT (10 * 1000) + +namespace +{ + + struct SendContext + { + json_t * response; + bool result; + bool is_called; + + explicit SendContext(bool result_ = true) + : response(nullptr) + , result(result_) + , is_called(false) + { + } + + ~SendContext() + { + if (nullptr != response) + { + json_decref(response); + } + } + }; + + bool jsonrpc_send( + json_t * request, + void * user_data) + { + SendContext * context = reinterpret_cast(user_data); + context->is_called = true; + context->response = request; + json_incref(request); + + return context->result; + } + + struct FinishedContext + { + bool is_called; + wf_status status; + json_t * result; + + FinishedContext() + : is_called(false) + , status(WF_BAD) + , result(nullptr) + { + + } + + ~FinishedContext() + { + if (nullptr != result) + { + json_decref(result); + } + } + }; + + void jsonrpc_finished( + void * user_data, + wf_status status, + struct json_t const * result) + { + FinishedContext * context = reinterpret_cast(user_data); + context->is_called = true; + context->status = status; + context->result = json_deep_copy(result); + } +} + +TEST(jsonrpc_proxy, init) +{ + struct wf_impl_timeout_manager timeout_manager; + wf_impl_timeout_manager_init(&timeout_manager); + + SendContext context; + void * user_data = reinterpret_cast(&context); + struct wf_impl_jsonrpc_proxy proxy; + wf_impl_jsonrpc_proxy_init(&proxy, &timeout_manager, WF_DEFAULT_TIMEOUT, &jsonrpc_send, user_data); + + wf_impl_jsonrpc_proxy_cleanup(&proxy); + wf_impl_timeout_manager_cleanup(&timeout_manager); + + ASSERT_FALSE(context.is_called); +} + +TEST(jsonrpc_proxy, invoke) +{ + struct wf_impl_timeout_manager timeout_manager; + wf_impl_timeout_manager_init(&timeout_manager); + + SendContext send_context; + void * send_data = reinterpret_cast(&send_context); + struct wf_impl_jsonrpc_proxy proxy; + wf_impl_jsonrpc_proxy_init(&proxy, &timeout_manager, WF_DEFAULT_TIMEOUT, &jsonrpc_send, send_data); + + FinishedContext finished_context; + void * finished_data = reinterpret_cast(&finished_context); + wf_impl_jsonrpc_proxy_invoke(&proxy, &jsonrpc_finished, finished_data, "foo", "si", "bar", 42); + + ASSERT_TRUE(send_context.is_called); + ASSERT_TRUE(json_is_object(send_context.response)); + + json_t * method = json_object_get(send_context.response, "method"); + ASSERT_TRUE(json_is_string(method)); + ASSERT_STREQ("foo", json_string_value(method)); + + json_t * params = json_object_get(send_context.response, "params"); + ASSERT_TRUE(json_is_array(params)); + ASSERT_EQ(2, json_array_size(params)); + ASSERT_TRUE(json_is_string(json_array_get(params, 0))); + ASSERT_STREQ("bar", json_string_value(json_array_get(params, 0))); + ASSERT_TRUE(json_is_integer(json_array_get(params, 1))); + ASSERT_EQ(42, json_integer_value(json_array_get(params, 1))); + + json_t * id = json_object_get(send_context.response, "id"); + ASSERT_TRUE(json_is_integer(id)); + + ASSERT_FALSE(finished_context.is_called); + + wf_impl_jsonrpc_proxy_cleanup(&proxy); + wf_impl_timeout_manager_cleanup(&timeout_manager); + + ASSERT_TRUE(finished_context.is_called); + ASSERT_FALSE(WF_GOOD == finished_context.status); +} + +TEST(jsonrpc_proxy, invoke_calls_finish_if_send_fails) +{ + struct wf_impl_timeout_manager timeout_manager; + wf_impl_timeout_manager_init(&timeout_manager); + + SendContext send_context(false); + void * send_data = reinterpret_cast(&send_context); + struct wf_impl_jsonrpc_proxy proxy; + wf_impl_jsonrpc_proxy_init(&proxy, &timeout_manager, WF_DEFAULT_TIMEOUT, &jsonrpc_send, send_data); + + FinishedContext finished_context; + void * finished_data = reinterpret_cast(&finished_context); + wf_impl_jsonrpc_proxy_invoke(&proxy, &jsonrpc_finished, finished_data, "foo", "si", "bar", 42); + + ASSERT_TRUE(send_context.is_called); + ASSERT_TRUE(json_is_object(send_context.response)); + + ASSERT_TRUE(finished_context.is_called); + ASSERT_FALSE(WF_GOOD == finished_context.status); + + wf_impl_jsonrpc_proxy_cleanup(&proxy); + wf_impl_timeout_manager_cleanup(&timeout_manager); +} + +TEST(jsonrpc_proxy, invoke_fails_if_another_request_is_pending) +{ + struct wf_impl_timeout_manager timeout_manager; + wf_impl_timeout_manager_init(&timeout_manager); + + SendContext send_context; + void * send_data = reinterpret_cast(&send_context); + struct wf_impl_jsonrpc_proxy proxy; + wf_impl_jsonrpc_proxy_init(&proxy, &timeout_manager, WF_DEFAULT_TIMEOUT, &jsonrpc_send, send_data); + + FinishedContext finished_context; + void * finished_data = reinterpret_cast(&finished_context); + wf_impl_jsonrpc_proxy_invoke(&proxy, &jsonrpc_finished, finished_data, "foo", "si", "bar", 42); + + FinishedContext finished_context2; + void * finished_data2 = reinterpret_cast(&finished_context2); + wf_impl_jsonrpc_proxy_invoke(&proxy, &jsonrpc_finished, finished_data2, "foo", ""); + + ASSERT_TRUE(send_context.is_called); + ASSERT_TRUE(json_is_object(send_context.response)); + + ASSERT_FALSE(finished_context.is_called); + + ASSERT_TRUE(finished_context2.is_called); + ASSERT_EQ(WF_BAD_BUSY, finished_context2.status); + + wf_impl_jsonrpc_proxy_cleanup(&proxy); + wf_impl_timeout_manager_cleanup(&timeout_manager); +} + +TEST(jsonrpc_proxy, invoke_fails_if_request_is_invalid) +{ + struct wf_impl_timeout_manager timeout_manager; + wf_impl_timeout_manager_init(&timeout_manager); + + SendContext send_context; + void * send_data = reinterpret_cast(&send_context); + struct wf_impl_jsonrpc_proxy proxy; + wf_impl_jsonrpc_proxy_init(&proxy, &timeout_manager, WF_DEFAULT_TIMEOUT, &jsonrpc_send, send_data); + + FinishedContext finished_context; + void * finished_data = reinterpret_cast(&finished_context); + wf_impl_jsonrpc_proxy_invoke(&proxy, &jsonrpc_finished, finished_data, "foo", "?", "error"); + + ASSERT_FALSE(send_context.is_called); + + ASSERT_TRUE(finished_context.is_called); + ASSERT_EQ(WF_BAD, finished_context.status); + + wf_impl_jsonrpc_proxy_cleanup(&proxy); + wf_impl_timeout_manager_cleanup(&timeout_manager); +} + +TEST(jsonrpc_proxy, on_result) +{ + struct wf_impl_timeout_manager timeout_manager; + wf_impl_timeout_manager_init(&timeout_manager); + + SendContext send_context; + void * send_data = reinterpret_cast(&send_context); + struct wf_impl_jsonrpc_proxy proxy; + wf_impl_jsonrpc_proxy_init(&proxy, &timeout_manager, WF_DEFAULT_TIMEOUT, &jsonrpc_send, send_data); + + FinishedContext finished_context; + void * finished_data = reinterpret_cast(&finished_context); + wf_impl_jsonrpc_proxy_invoke(&proxy, &jsonrpc_finished, finished_data, "foo", "si", "bar", 42); + + ASSERT_TRUE(send_context.is_called); + ASSERT_TRUE(json_is_object(send_context.response)); + + json_t * id = json_object_get(send_context.response, "id"); + ASSERT_TRUE(json_is_number(id)); + + json_t * response = json_object(); + json_object_set_new(response, "result", json_string("okay")); + json_object_set(response, "id", id); + + wf_impl_jsonrpc_proxy_onresult(&proxy, response); + json_decref(response); + + ASSERT_TRUE(finished_context.is_called); + ASSERT_EQ(WF_GOOD, finished_context.status); + ASSERT_TRUE(json_is_string(finished_context.result)); + ASSERT_STREQ("okay", json_string_value(finished_context.result)); + + wf_impl_jsonrpc_proxy_cleanup(&proxy); + wf_impl_timeout_manager_cleanup(&timeout_manager); +} + +TEST(jsonrpc_proxy, on_result_reject_response_with_unknown_id) +{ + struct wf_impl_timeout_manager timeout_manager; + wf_impl_timeout_manager_init(&timeout_manager); + + SendContext send_context; + void * send_data = reinterpret_cast(&send_context); + struct wf_impl_jsonrpc_proxy proxy; + wf_impl_jsonrpc_proxy_init(&proxy, &timeout_manager, WF_DEFAULT_TIMEOUT, &jsonrpc_send, send_data); + + FinishedContext finished_context; + void * finished_data = reinterpret_cast(&finished_context); + wf_impl_jsonrpc_proxy_invoke(&proxy, &jsonrpc_finished, finished_data, "foo", "si", "bar", 42); + + ASSERT_TRUE(send_context.is_called); + ASSERT_TRUE(json_is_object(send_context.response)); + + json_t * id = json_object_get(send_context.response, "id"); + ASSERT_TRUE(json_is_number(id)); + + json_t * response = json_object(); + json_object_set_new(response, "result", json_string("okay")); + json_object_set_new(response, "id", json_integer(1 + json_integer_value(id))); + + wf_impl_jsonrpc_proxy_onresult(&proxy, response); + json_decref(response); + + ASSERT_FALSE(finished_context.is_called); + + wf_impl_jsonrpc_proxy_cleanup(&proxy); + wf_impl_timeout_manager_cleanup(&timeout_manager); +} + +TEST(jsonrpc_proxy, timeout) +{ + struct wf_impl_timeout_manager timeout_manager; + wf_impl_timeout_manager_init(&timeout_manager); + + SendContext send_context; + void * send_data = reinterpret_cast(&send_context); + struct wf_impl_jsonrpc_proxy proxy; + wf_impl_jsonrpc_proxy_init(&proxy, &timeout_manager, 0, &jsonrpc_send, send_data); + + FinishedContext finished_context; + void * finished_data = reinterpret_cast(&finished_context); + wf_impl_jsonrpc_proxy_invoke(&proxy, &jsonrpc_finished, finished_data, "foo", "si", "bar", 42); + + ASSERT_TRUE(send_context.is_called); + ASSERT_TRUE(json_is_object(send_context.response)); + + msleep(10); + wf_impl_timeout_manager_check(&timeout_manager); + + ASSERT_TRUE(finished_context.is_called); + ASSERT_EQ(WF_BAD_TIMEOUT, finished_context.status); + + wf_impl_jsonrpc_proxy_cleanup(&proxy); + wf_impl_timeout_manager_cleanup(&timeout_manager); +} + +TEST(jsonrpc_proxy, cleanup_pending_request) +{ + struct wf_impl_timeout_manager timeout_manager; + wf_impl_timeout_manager_init(&timeout_manager); + + SendContext send_context; + void * send_data = reinterpret_cast(&send_context); + struct wf_impl_jsonrpc_proxy proxy; + wf_impl_jsonrpc_proxy_init(&proxy, &timeout_manager, 10, &jsonrpc_send, send_data); + + FinishedContext finished_context; + void * finished_data = reinterpret_cast(&finished_context); + wf_impl_jsonrpc_proxy_invoke(&proxy, &jsonrpc_finished, finished_data, "foo", "si", "bar", 42); + + ASSERT_TRUE(send_context.is_called); + ASSERT_TRUE(json_is_object(send_context.response)); + + ASSERT_FALSE(finished_context.is_called); + ASSERT_NE(nullptr, timeout_manager.timers); + + wf_impl_jsonrpc_proxy_cleanup(&proxy); + + ASSERT_TRUE(finished_context.is_called); + ASSERT_EQ(nullptr, timeout_manager.timers); + + wf_impl_timeout_manager_cleanup(&timeout_manager); +} + + + +TEST(jsonrpc_proxy, notify) +{ + struct wf_impl_timeout_manager timeout_manager; + wf_impl_timeout_manager_init(&timeout_manager); + + SendContext send_context; + void * send_data = reinterpret_cast(&send_context); + struct wf_impl_jsonrpc_proxy proxy; + wf_impl_jsonrpc_proxy_init(&proxy, &timeout_manager, WF_DEFAULT_TIMEOUT, &jsonrpc_send, send_data); + + wf_impl_jsonrpc_proxy_notify(&proxy, "foo", "si", "bar", 42); + + ASSERT_TRUE(send_context.is_called); + ASSERT_TRUE(json_is_object(send_context.response)); + + json_t * method = json_object_get(send_context.response, "method"); + ASSERT_TRUE(json_is_string(method)); + ASSERT_STREQ("foo", json_string_value(method)); + + json_t * params = json_object_get(send_context.response, "params"); + ASSERT_TRUE(json_is_array(params)); + ASSERT_EQ(2, json_array_size(params)); + ASSERT_TRUE(json_is_string(json_array_get(params, 0))); + ASSERT_STREQ("bar", json_string_value(json_array_get(params, 0))); + ASSERT_TRUE(json_is_integer(json_array_get(params, 1))); + ASSERT_EQ(42, json_integer_value(json_array_get(params, 1))); + + json_t * id = json_object_get(send_context.response, "id"); + ASSERT_EQ(nullptr, id); + + + wf_impl_jsonrpc_proxy_cleanup(&proxy); + wf_impl_timeout_manager_cleanup(&timeout_manager); +} + +TEST(jsonrpc_proxy, notify_dont_send_invalid_request) +{ + struct wf_impl_timeout_manager timeout_manager; + wf_impl_timeout_manager_init(&timeout_manager); + + SendContext send_context; + void * send_data = reinterpret_cast(&send_context); + struct wf_impl_jsonrpc_proxy proxy; + wf_impl_jsonrpc_proxy_init(&proxy, &timeout_manager, WF_DEFAULT_TIMEOUT, &jsonrpc_send, send_data); + + wf_impl_jsonrpc_proxy_notify(&proxy, "foo", "?"); + + ASSERT_FALSE(send_context.is_called); + + wf_impl_jsonrpc_proxy_cleanup(&proxy); + wf_impl_timeout_manager_cleanup(&timeout_manager); +} diff --git a/test/adapter/jsonrpc/test_request.cc b/test/adapter/jsonrpc/test_request.cc new file mode 100644 index 0000000..07c3da8 --- /dev/null +++ b/test/adapter/jsonrpc/test_request.cc @@ -0,0 +1,102 @@ +#include +#include "webfuse/adapter/impl/jsonrpc/request.h" + +namespace +{ + +struct Context +{ + json_t * response; +}; + +bool jsonrpc_send( + json_t * request, + void * user_data) +{ + Context * context = reinterpret_cast(user_data); + context->response = request; + json_incref(request); + + return true; +} + +} + +TEST(jsonrpc_request, create_dispose) +{ + Context context{nullptr}; + void * user_data = reinterpret_cast(&context); + + struct wf_impl_jsonrpc_request * request = + wf_impl_jsonrpc_request_create(42, &jsonrpc_send, user_data); + + ASSERT_NE(nullptr, request); + ASSERT_EQ(user_data, wf_impl_jsonrpc_request_get_userdata(request)); + + wf_impl_jsonrpc_request_dispose(request); +} + +TEST(jsonrpc_request, respond) +{ + Context context{nullptr}; + void * user_data = reinterpret_cast(&context); + + struct wf_impl_jsonrpc_request * request = + wf_impl_jsonrpc_request_create(42, &jsonrpc_send, user_data); + + wf_impl_jsonrpc_respond(request, json_string("okay")); + + ASSERT_NE(nullptr, context.response); + + + json_t * response = reinterpret_cast(context.response); + ASSERT_TRUE(json_is_object(response)); + + json_t * id = json_object_get(response, "id"); + ASSERT_TRUE(json_is_integer(id)); + ASSERT_EQ(42, json_integer_value(id)); + + json_t * result = json_object_get(response, "result"); + ASSERT_TRUE(json_is_string(result)); + ASSERT_STREQ("okay", json_string_value(result)); + + ASSERT_EQ(nullptr, json_object_get(response, "error")); + + json_decref(response); +} + +TEST(jsonrpc_request, respond_error) +{ + Context context{nullptr}; + void * user_data = reinterpret_cast(&context); + + struct wf_impl_jsonrpc_request * request = + wf_impl_jsonrpc_request_create(42, &jsonrpc_send, user_data); + + wf_impl_jsonrpc_respond_error(request, WF_BAD); + + ASSERT_NE(nullptr, context.response); + + + json_t * response = reinterpret_cast(context.response); + ASSERT_TRUE(json_is_object(response)); + + json_t * id = json_object_get(response, "id"); + ASSERT_TRUE(json_is_integer(id)); + ASSERT_EQ(42, json_integer_value(id)); + + ASSERT_EQ(nullptr, json_object_get(response, "result")); + + json_t * err = json_object_get(response, "error"); + ASSERT_TRUE(json_is_object(err)); + + json_t * err_code = json_object_get(err, "code"); + ASSERT_TRUE(json_is_integer(err_code)); + ASSERT_EQ(WF_BAD, json_integer_value(err_code)); + + json_t * err_message = json_object_get(err, "message"); + ASSERT_TRUE(json_is_string(err_message)); + ASSERT_STREQ("Bad", json_string_value(err_message)); + + json_decref(response); +} diff --git a/test/adapter/jsonrpc/test_response.cc b/test/adapter/jsonrpc/test_response.cc new file mode 100644 index 0000000..3a63d0f --- /dev/null +++ b/test/adapter/jsonrpc/test_response.cc @@ -0,0 +1,56 @@ +#include +#include "webfuse/adapter/impl/jsonrpc/response.h" + +TEST(json_response, init_result) +{ + json_t * message = json_object(); + json_object_set_new(message, "result", json_integer(47)); + json_object_set_new(message, "id", json_integer(11)); + + struct wf_impl_jsonrpc_response response; + wf_impl_jsonrpc_response_init(&response, message); + + ASSERT_EQ(WF_GOOD, response.status); + ASSERT_TRUE(json_is_integer(response.result)); + ASSERT_EQ(47, json_integer_value(response.result)); + ASSERT_EQ(11, response.id); + + wf_impl_jsonrpc_response_cleanup(&response); + json_decref(message); +} + +TEST(json_response, init_error) +{ + json_t * message = json_object(); + json_t * err = json_object(); + json_object_set_new(err, "code", json_integer(WF_BAD_ACCESS_DENIED)); + json_object_set_new(err, "message", json_string("access denied")); + json_object_set_new(message, "error", err); + json_object_set_new(message, "id", json_integer(23)); + + struct wf_impl_jsonrpc_response response; + wf_impl_jsonrpc_response_init(&response, message); + + ASSERT_EQ(WF_BAD_ACCESS_DENIED, response.status); + ASSERT_EQ(nullptr, response.result); + ASSERT_EQ(23, response.id); + + wf_impl_jsonrpc_response_cleanup(&response); + json_decref(message); +} + +TEST(json_response, init_format_error) +{ + json_t * message = json_object(); + json_object_set_new(message, "id", json_integer(12)); + + struct wf_impl_jsonrpc_response response; + wf_impl_jsonrpc_response_init(&response, message); + + ASSERT_EQ(WF_BAD_FORMAT, response.status); + ASSERT_EQ(nullptr, response.result); + ASSERT_EQ(12, response.id); + + wf_impl_jsonrpc_response_cleanup(&response); + json_decref(message); +} diff --git a/test/adapter/jsonrpc/test_server.cc b/test/adapter/jsonrpc/test_server.cc new file mode 100644 index 0000000..20d5d0f --- /dev/null +++ b/test/adapter/jsonrpc/test_server.cc @@ -0,0 +1,125 @@ +#include +#include "webfuse/adapter/impl/jsonrpc/server.h" +#include "webfuse/adapter/impl/jsonrpc/request.h" + +namespace +{ + struct Context + { + json_t * response; + bool is_called; + }; + + bool jsonrpc_send( + json_t * request, + void * user_data) + { + Context * context = reinterpret_cast(user_data); + context->is_called = true; + context->response = request; + json_incref(request); + + return true; + } + + void sayHello( + struct wf_impl_jsonrpc_request * request, + char const * method_name, + json_t * params, + void * user_data) + { + (void) method_name; + (void) params; + (void) user_data; + + json_t * result = json_string("Hello"); + wf_impl_jsonrpc_respond(request, result); + } + +} + +TEST(jsonrpc_server, process_request) +{ + struct wf_impl_jsonrpc_server server; + wf_impl_jsonrpc_server_init(&server); + wf_impl_jsonrpc_server_add(&server, "sayHello", &sayHello, nullptr); + + Context context{nullptr, false}; + void * user_data = reinterpret_cast(&context); + json_t * request = json_object(); + json_object_set_new(request, "method", json_string("sayHello")); + json_object_set_new(request, "params", json_array()); + json_object_set_new(request, "id", json_integer(23)); + wf_impl_jsonrpc_server_process(&server, request, &jsonrpc_send, user_data); + + ASSERT_TRUE(context.is_called); + ASSERT_NE(nullptr, context.response); + ASSERT_TRUE(json_is_object(context.response)); + + json_t * id = json_object_get(context.response, "id"); + ASSERT_TRUE(json_is_integer(id)); + ASSERT_EQ(23, json_integer_value(id)); + + json_t * result = json_object_get(context.response, "result"); + ASSERT_TRUE(json_is_string(result)); + ASSERT_STREQ("Hello", json_string_value(result)); + + json_decref(context.response); + json_decref(request); + wf_impl_jsonrpc_server_cleanup(&server); +} + +TEST(jsonrpc_server, invoke_unknown_method) +{ + struct wf_impl_jsonrpc_server server; + wf_impl_jsonrpc_server_init(&server); + wf_impl_jsonrpc_server_add(&server, "sayHello", &sayHello, nullptr); + + Context context{nullptr, false}; + void * user_data = reinterpret_cast(&context); + json_t * request = json_object(); + json_object_set_new(request, "method", json_string("greet")); + json_object_set_new(request, "params", json_array()); + json_object_set_new(request, "id", json_integer(42)); + wf_impl_jsonrpc_server_process(&server, request, &jsonrpc_send, user_data); + + ASSERT_TRUE(context.is_called); + ASSERT_NE(nullptr, context.response); + ASSERT_TRUE(json_is_object(context.response)); + + json_t * id = json_object_get(context.response, "id"); + ASSERT_TRUE(json_is_integer(id)); + ASSERT_EQ(42, json_integer_value(id)); + + json_t * err = json_object_get(context.response, "error"); + ASSERT_TRUE(json_is_object(err)); + + json_t * err_code = json_object_get(err, "code"); + ASSERT_TRUE(json_is_integer(err_code)); + ASSERT_EQ(WF_BAD_NOTIMPLEMENTED, json_integer_value(err_code)); + + json_t * err_message = json_object_get(err, "message"); + ASSERT_TRUE(json_is_string(err_message)); + + json_decref(context.response); + json_decref(request); + wf_impl_jsonrpc_server_cleanup(&server); +} + +TEST(jsonrpc_server, skip_invalid_request) +{ + struct wf_impl_jsonrpc_server server; + wf_impl_jsonrpc_server_init(&server); + + Context context{nullptr, false}; + void * user_data = reinterpret_cast(&context); + json_t * request = json_object(); + json_object_set_new(request, "method", json_string("sayHello")); + json_object_set_new(request, "params", json_array()); + wf_impl_jsonrpc_server_process(&server, request, &jsonrpc_send, user_data); + + ASSERT_FALSE(context.is_called); + + json_decref(request); + wf_impl_jsonrpc_server_cleanup(&server); +} diff --git a/test/adapter/jsonrpc/test_util.cc b/test/adapter/jsonrpc/test_util.cc new file mode 100644 index 0000000..562b7c4 --- /dev/null +++ b/test/adapter/jsonrpc/test_util.cc @@ -0,0 +1,47 @@ +#include +#include "webfuse/adapter/impl/jsonrpc/util.h" + +TEST(jsonrpc_util, get_int) +{ + json_t * object = json_object(); + json_object_set_new(object, "key", json_integer(23)); + int value = wf_impl_json_get_int(object, "key", 42); + ASSERT_EQ(23, value); + + json_decref(object); +} + +TEST(jsonrpc_util, failed_to_get_null_object) +{ + int value = wf_impl_json_get_int(nullptr, "key", 42); + + ASSERT_EQ(42, value); +} + +TEST(jsonrpc_util, failed_to_get_not_object) +{ + json_t * object = json_array(); + int value = wf_impl_json_get_int(nullptr, "key", 42); + ASSERT_EQ(42, value); + + json_decref(object); +} + +TEST(jsonrpc_util, failed_to_get_invalid_key) +{ + json_t * object = json_object(); + int value = wf_impl_json_get_int(object, "key", 42); + ASSERT_EQ(42, value); + + json_decref(object); +} + +TEST(jsonrpc_util, failed_to_get_invalid_value_type) +{ + json_t * object = json_object(); + json_object_set_new(object, "key", json_string("42")); + int value = wf_impl_json_get_int(object, "key", 42); + ASSERT_EQ(42, value); + + json_decref(object); +} \ No newline at end of file diff --git a/test/test_authenticator.cc b/test/adapter/test_authenticator.cc similarity index 100% rename from test/test_authenticator.cc rename to test/adapter/test_authenticator.cc diff --git a/test/test_authenticators.cc b/test/adapter/test_authenticators.cc similarity index 100% rename from test/test_authenticators.cc rename to test/adapter/test_authenticators.cc diff --git a/test/test_credentials.cc b/test/adapter/test_credentials.cc similarity index 100% rename from test/test_credentials.cc rename to test/adapter/test_credentials.cc diff --git a/test/test_fuse_req.cc b/test/adapter/test_fuse_req.cc similarity index 70% rename from test/test_fuse_req.cc rename to test/adapter/test_fuse_req.cc index efb63af..d1264d9 100644 --- a/test/test_fuse_req.cc +++ b/test/adapter/test_fuse_req.cc @@ -1,5 +1,5 @@ #include -#include "webfuse/adapter/fuse_wrapper.h" +#include "webfuse/adapter/impl/fuse_wrapper.h" TEST(libfuse, fuse_req_t_size) { diff --git a/test/test_response_parser.cc b/test/adapter/test_response_parser.cc similarity index 100% rename from test/test_response_parser.cc rename to test/adapter/test_response_parser.cc diff --git a/test/test_server.cc b/test/adapter/test_server.cc similarity index 100% rename from test/test_server.cc rename to test/adapter/test_server.cc diff --git a/test/test_timepoint.cc b/test/adapter/test_timepoint.cc similarity index 100% rename from test/test_timepoint.cc rename to test/adapter/test_timepoint.cc diff --git a/test/test_timer.cc b/test/adapter/test_timer.cc similarity index 66% rename from test/test_timer.cc rename to test/adapter/test_timer.cc index d33dea8..b838147 100644 --- a/test/test_timer.cc +++ b/test/adapter/test_timer.cc @@ -49,6 +49,23 @@ TEST(timer, trigger) wf_impl_timeout_manager_cleanup(&manager); } +TEST(timer, trigger_on_cleanup) +{ + struct wf_impl_timeout_manager manager; + struct wf_impl_timer timer; + + wf_impl_timeout_manager_init(&manager); + wf_impl_timer_init(&timer, &manager); + + bool triggered = false; + wf_impl_timer_start(&timer, wf_impl_timepoint_in_msec(5 * 60 * 1000), &on_timeout, reinterpret_cast(&triggered)); + + wf_impl_timeout_manager_cleanup(&manager); + ASSERT_TRUE(triggered); + + wf_impl_timer_cleanup(&timer); +} + TEST(timer, cancel) { struct wf_impl_timeout_manager manager; @@ -69,6 +86,37 @@ TEST(timer, cancel) wf_impl_timeout_manager_cleanup(&manager); } +TEST(timer, cancel_multiple_timers) +{ + static size_t const count = 5; + struct wf_impl_timeout_manager manager; + struct wf_impl_timer timer[count]; + + wf_impl_timeout_manager_init(&manager); + + bool triggered = false; + for(size_t i = 0; i < count; i++) + { + wf_impl_timer_init(&timer[i], &manager); + wf_impl_timer_start(&timer[i], wf_impl_timepoint_in_msec(0), &on_timeout, &triggered); + } + + msleep(10); + for(size_t i = 0; i < count; i++) + { + wf_impl_timer_cancel(&timer[i]); + } + + wf_impl_timeout_manager_check(&manager); + ASSERT_FALSE(triggered); + + for(size_t i = 0; i < count; i++) + { + wf_impl_timer_cleanup(&timer[0]); + } + wf_impl_timeout_manager_cleanup(&manager); +} + TEST(timer, multiple_timers) { static size_t const count = 5; diff --git a/test/test_container_of.cc b/test/core/test_container_of.cc similarity index 100% rename from test/test_container_of.cc rename to test/core/test_container_of.cc diff --git a/test/core/test_message.cc b/test/core/test_message.cc new file mode 100644 index 0000000..6212280 --- /dev/null +++ b/test/core/test_message.cc @@ -0,0 +1,22 @@ +#include +#include +#include "webfuse/core/message.h" + +TEST(wf_message, create) +{ + json_t * value = json_object(); + + struct wf_message * message = wf_message_create(value); + ASSERT_NE(nullptr, message); + ASSERT_EQ(2, message->length); + ASSERT_TRUE(0 == strncmp("{}", message->data, 2)); + + wf_message_dispose(message); + json_decref(value); +} + +TEST(wf_message, fail_to_create) +{ + struct wf_message * message = wf_message_create(nullptr); + ASSERT_EQ(nullptr, message); +} \ No newline at end of file diff --git a/test/core/test_message_queue.cc b/test/core/test_message_queue.cc new file mode 100644 index 0000000..ef07bed --- /dev/null +++ b/test/core/test_message_queue.cc @@ -0,0 +1,52 @@ +#include +#include "webfuse/core/message_queue.h" +#include "webfuse/core/message.h" +#include "webfuse/core/slist.h" + +namespace +{ + + struct wf_slist_item * create_message(char const * content) + { + json_t * value = json_object(); + json_object_set_new(value, "content", json_string(content)); + struct wf_message * message = wf_message_create(value); + + json_decref(value); + return &message->item; + } + +} + +TEST(wf_message_queue, cleanup_empty_list) +{ + struct wf_slist queue; + wf_slist_init(&queue); + + wf_message_queue_cleanup(&queue); + ASSERT_TRUE(wf_slist_empty(&queue)); +} + +TEST(wf_message_queue, cleanup_one_element) +{ + struct wf_slist queue; + wf_slist_init(&queue); + + wf_slist_append(&queue, create_message("Hello")); + + wf_message_queue_cleanup(&queue); + ASSERT_TRUE(wf_slist_empty(&queue)); +} + +TEST(wf_message_queue, cleanup_multiple_element) +{ + struct wf_slist queue; + wf_slist_init(&queue); + + wf_slist_append(&queue, create_message("Hello")); + wf_slist_append(&queue, create_message("World")); + wf_slist_append(&queue, create_message("!")); + + wf_message_queue_cleanup(&queue); + ASSERT_TRUE(wf_slist_empty(&queue)); +} \ No newline at end of file diff --git a/test/test_path.cc b/test/core/test_path.cc similarity index 100% rename from test/test_path.cc rename to test/core/test_path.cc diff --git a/test/test_slist.cc b/test/core/test_slist.cc similarity index 100% rename from test/test_slist.cc rename to test/core/test_slist.cc diff --git a/test/core/test_status.cc b/test/core/test_status.cc new file mode 100644 index 0000000..4558f72 --- /dev/null +++ b/test/core/test_status.cc @@ -0,0 +1,30 @@ +#include +#include "webfuse/core/status_intern.h" + +TEST(wf_status, tostring) +{ + ASSERT_STREQ("Good", wf_status_tostring(WF_GOOD)); + ASSERT_STREQ("Bad", wf_status_tostring(WF_BAD)); + ASSERT_STREQ("Bad (not implemented)", wf_status_tostring(WF_BAD_NOTIMPLEMENTED)); + ASSERT_STREQ("Bad (busy)", wf_status_tostring(WF_BAD_BUSY)); + ASSERT_STREQ("Bad (timeout)", wf_status_tostring(WF_BAD_TIMEOUT)); + ASSERT_STREQ("Bad (format)", wf_status_tostring(WF_BAD_FORMAT)); + ASSERT_STREQ("Bad (no entry)", wf_status_tostring(WF_BAD_NOENTRY)); + ASSERT_STREQ("Bad (access denied)", wf_status_tostring(WF_BAD_ACCESS_DENIED)); + + ASSERT_STREQ("Bad (unknown)", wf_status_tostring(-1)); +} + +TEST(wf_status, to_rc) +{ + ASSERT_EQ(0, wf_status_to_rc(WF_GOOD)); + ASSERT_EQ(-ENOENT, wf_status_to_rc(WF_BAD)); + ASSERT_EQ(-ENOSYS, wf_status_to_rc(WF_BAD_NOTIMPLEMENTED)); + ASSERT_EQ(-ENOENT, wf_status_to_rc(WF_BAD_BUSY)); + ASSERT_EQ(-ETIMEDOUT, wf_status_to_rc(WF_BAD_TIMEOUT)); + ASSERT_EQ(-ENOENT, wf_status_to_rc(WF_BAD_FORMAT)); + ASSERT_EQ(-ENOENT, wf_status_to_rc(WF_BAD_NOENTRY)); + ASSERT_EQ(-EACCES, wf_status_to_rc(WF_BAD_ACCESS_DENIED)); + + ASSERT_EQ(-ENOENT, wf_status_to_rc(-1)); +} \ No newline at end of file diff --git a/test/test_string.cc b/test/core/test_string.cc similarity index 100% rename from test/test_string.cc rename to test/core/test_string.cc diff --git a/test/die_if.cc b/test/die_if.cc new file mode 100644 index 0000000..3eff5ae --- /dev/null +++ b/test/die_if.cc @@ -0,0 +1,15 @@ +#include "die_if.hpp" +#include + +namespace webfuse_test +{ + +void die_if(bool expression) +{ + if (expression) + { + exit(EXIT_FAILURE); + } +} + +} \ No newline at end of file diff --git a/test/die_if.hpp b/test/die_if.hpp new file mode 100644 index 0000000..95d3ff2 --- /dev/null +++ b/test/die_if.hpp @@ -0,0 +1,11 @@ +#ifndef WF_TEST_DIE_IF_HPP +#define WF_TEST_DIE_IF_HPP + +namespace webfuse_test +{ + +extern void die_if(bool expression); + +} + +#endif diff --git a/test/integration/provider.cc b/test/integration/provider.cc new file mode 100644 index 0000000..3ee4285 --- /dev/null +++ b/test/integration/provider.cc @@ -0,0 +1,85 @@ +#include "integration/provider.hpp" +#include "webfuse_provider.h" +#include "webfuse/provider/impl/client.h" +#include +#include +#include +#include "msleep.hpp" + +namespace webfuse_test +{ + +class Provider::Private +{ +public: + explicit Private(char const * url) + : is_shutdown_requested(false) + { + config = wfp_client_config_create(); + + fs = wfp_static_filesystem_create(config); + wfp_static_filesystem_add_text(fs, "hello.txt", 0444, "Hello, World"); + + client = wfp_client_create(config); + wfp_client_connect(client, url); + while (!wfp_impl_client_is_connected(client)) + { + wfp_client_service(client, 100); + } + + thread = std::thread(Run, this); + webfuse_test::msleep(200); + } + + ~Private() + { + RequestShutdown(); + thread.join(); + + wfp_client_dispose(client); + + wfp_static_filesystem_dispose(fs); + wfp_client_config_dispose(config); + } + + bool IsShutdownRequested() + { + std::lock_guard lock(shutdown_lock); + return is_shutdown_requested; + } +private: + void RequestShutdown() + { + std::lock_guard lock(shutdown_lock); + is_shutdown_requested = true; + } + + static void Run(Provider::Private * context) + { + while (!context->IsShutdownRequested()) + { + wfp_client_service(context->client, 100); + } + } + + std::mutex shutdown_lock; + std::thread thread; + bool is_shutdown_requested; + + wfp_client_config * config; + wfp_static_filesystem * fs; +public: + wfp_client * client; +}; + +Provider::Provider(char const * url) +: d(new Provider::Private(url)) +{ +} + +Provider::~Provider() +{ + delete d; +} + +} \ No newline at end of file diff --git a/test/integration/provider.hpp b/test/integration/provider.hpp new file mode 100644 index 0000000..c7c60c4 --- /dev/null +++ b/test/integration/provider.hpp @@ -0,0 +1,19 @@ +#ifndef WF_TEST_INTEGRATION_PROVIDER +#define WF_TEST_INTEGRATION_PROVIDER + +namespace webfuse_test +{ + +class Provider +{ +public: + explicit Provider(char const * url); + ~Provider(); +private: + class Private; + Private * d; +}; + +} + +#endif diff --git a/test/integration/server.cc b/test/integration/server.cc new file mode 100644 index 0000000..0659efe --- /dev/null +++ b/test/integration/server.cc @@ -0,0 +1,99 @@ +#include "integration/server.hpp" +#include +#include +#include +#include +#include +#include "webfuse_adapter.h" +#include "webfuse/adapter/impl/server.h" +#include "msleep.hpp" + +#define WF_PATH_MAX (100) + +namespace webfuse_test +{ + +class Server::Private +{ +public: + Private() + : is_shutdown_requested(false) + { + snprintf(base_dir, WF_PATH_MAX, "%s", "/tmp/webfuse_test_integration_XXXXXX"); + mkdtemp(base_dir); + + config = wf_server_config_create(); + wf_server_config_set_port(config, 8080); + wf_server_config_set_mountpoint(config, base_dir); + + server = wf_server_create(config); + + while (!wf_impl_server_is_operational(server)) + { + wf_server_service(server, 100); + } + + thread = std::thread(Run, this); + + } + + ~Private() + { + RequestShutdown(); + thread.join(); + rmdir(base_dir); + wf_server_dispose(server); + wf_server_config_dispose(config); + } + + bool IsShutdownRequested() + { + std::lock_guard lock(shutdown_lock); + return is_shutdown_requested; + } + +private: + void RequestShutdown() + { + std::lock_guard lock(shutdown_lock); + is_shutdown_requested = true; + } + + static void Run(Server::Private * context) + { + while (!context->IsShutdownRequested()) + { + wf_server_service(context->server, 100); + } + } + + + std::mutex shutdown_lock; + std::thread thread; + bool is_shutdown_requested; + + +public: + char base_dir[WF_PATH_MAX]; + wf_server_config * config; + wf_server * server; +}; + +Server::Server() +: d(new Server::Private()) +{ + +} + +Server::~Server() +{ + delete d; +} + +char const * Server::GetBaseDir(void) const +{ + return d->base_dir; +} + + +} \ No newline at end of file diff --git a/test/integration/server.hpp b/test/integration/server.hpp new file mode 100644 index 0000000..64f949d --- /dev/null +++ b/test/integration/server.hpp @@ -0,0 +1,22 @@ +#ifndef WF_TEST_INTEGRATION_SERVER_HPP +#define WF_TEST_INTEGRATION_SERVER_HPP + +namespace webfuse_test +{ + +class Server +{ +public: + Server(); + ~Server(); + void Start(void); + void Stop(void); + char const * GetBaseDir(void) const; +private: + class Private; + Private * d; +}; + +} + +#endif diff --git a/test/integration/test_integration.cc b/test/integration/test_integration.cc new file mode 100644 index 0000000..8bd21c6 --- /dev/null +++ b/test/integration/test_integration.cc @@ -0,0 +1,156 @@ +#include +#include "integration/server.hpp" +#include "integration/provider.hpp" + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include "webfuse/core/lws_log.h" +#include "die_if.hpp" + +using webfuse_test::Server; +using webfuse_test::Provider; +using webfuse_test::die_if; + +namespace +{ + class IntegrationTest: public ::testing::Test + { + public: + IntegrationTest() + : server(nullptr) + , provider(nullptr) + { + json_object_seed(0); + wf_lwslog_disable(); + } + + protected: + void SetUp() + { + server = new Server(); + provider = new Provider("ws://localhost:8080/"); + } + + void TearDown() + { + delete provider; + delete server; + } + + char const * GetBaseDir() const + { + return server->GetBaseDir(); + } + private: + Server * server; + Provider * provider; + }; +} + +TEST_F(IntegrationTest, HasMountpoint) +{ + struct stat buffer; + int rc = stat(GetBaseDir(), &buffer); + + ASSERT_EQ(0, rc); + ASSERT_TRUE(S_ISDIR(buffer.st_mode)); +} + +TEST_F(IntegrationTest, ProvidesTextFile) +{ + std::string file_name = std::string(GetBaseDir()) + "/cprovider/default/hello.txt"; + + ASSERT_EXIT({ + struct stat buffer; + int rc = stat(file_name.c_str(), &buffer); + + die_if(0 != rc); + die_if(!S_ISREG(buffer.st_mode)); + die_if(0444 != (buffer.st_mode & 0777)); + die_if(12 != buffer.st_size); + + exit(0); + }, ::testing::ExitedWithCode(0), ".*"); +} + +TEST_F(IntegrationTest, ReadTextFile) +{ + std::string file_name = std::string(GetBaseDir()) + "/cprovider/default/hello.txt"; + + ASSERT_EXIT({ + FILE * file = fopen(file_name.c_str(), "rb"); + die_if(nullptr == file); + + char buffer[13]; + ssize_t count = fread(buffer, 1, 12, file); + int rc = fclose(file); + + die_if(12 != count); + die_if(0 != strncmp("Hello, World", buffer, 12)); + die_if(0 != rc); + + exit(0); + }, ::testing::ExitedWithCode(0), ".*"); + +} + +TEST_F(IntegrationTest, ReadDir) +{ + std::string dir_name = std::string(GetBaseDir()) + "/cprovider/default"; + + ASSERT_EXIT({ + + DIR * dir = opendir(dir_name.c_str()); + die_if(nullptr == dir); + + bool found_self = false; + bool found_parent = false; + bool found_hello_txt = false; + bool found_other = false; + + dirent * entry = readdir(dir); + while (NULL != entry) + { + if (0 == strcmp(".", entry->d_name)) + { + found_self = true; + } + else if (0 == strcmp("..", entry->d_name)) + { + found_parent = true; + } + else if (0 == strcmp("hello.txt", entry->d_name)) + { + found_hello_txt = true; + } + else + { + found_other = true; + } + + + entry = readdir(dir); + } + + closedir(dir); + + die_if(!found_self); + die_if(!found_parent); + die_if(!found_hello_txt); + + die_if(found_other); + + exit(0); + }, ::testing::ExitedWithCode(0), ".*"); + +} \ No newline at end of file diff --git a/test/msleep.cc b/test/msleep.cc index cd989fb..5f76e49 100644 --- a/test/msleep.cc +++ b/test/msleep.cc @@ -10,7 +10,7 @@ void msleep(long millis) long const msecs_per_nsec = (1000 * 1000); long const seconds = millis / secs_per_msec; - long const nanos = (millis & secs_per_msec) * msecs_per_nsec; + long const nanos = (millis % secs_per_msec) * msecs_per_nsec; struct timespec timeout = { seconds, nanos }; while (0 != nanosleep(&timeout, &timeout)); diff --git a/test/test_static_filesystem.cc b/test/provider/test_static_filesystem.cc similarity index 100% rename from test/test_static_filesystem.cc rename to test/provider/test_static_filesystem.cc diff --git a/test/test_url.cc b/test/provider/test_url.cc similarity index 100% rename from test/test_url.cc rename to test/provider/test_url.cc