1
0
mirror of https://github.com/falk-werner/webfuse-provider synced 2026-03-02 04:09:18 +00:00

refactor: merged code structure

This commit is contained in:
Falk Werner
2020-06-16 23:39:45 +02:00
parent d041936abf
commit 2f80ebffcc
169 changed files with 384 additions and 418 deletions

View File

@@ -0,0 +1,85 @@
#include "webfuse_provider/tests/integration/file.hpp"
#include <cstdio>
#include <sstream>
namespace
{
bool invoke(std::string const & command)
{
int exit_code = -1;
FILE * file = ::popen(command.c_str(), "r");
if (nullptr != file)
{
exit_code = ::pclose(file);
}
return (0 == exit_code);
}
}
namespace webfuse_test
{
File::File(std::string const& path)
: path_(path)
{
}
File::~File()
{
}
bool File::isFile()
{
std::stringstream command;
command << "./fs_check -c is_file -f " << path_;
return invoke(command.str());
}
bool File::isDirectory()
{
std::stringstream command;
command << "./fs_check -c is_dir -f " << path_;
return invoke(command.str());
}
bool File::hasAccessRights(int accessRights)
{
std::stringstream command;
command << "./fs_check -c has_mode -f " << path_ << " -a " << accessRights;
return invoke(command.str());
}
bool File::hasSize(size_t size)
{
std::stringstream command;
command << "./fs_check -c has_size -f " << path_ << " -a " << size;
return invoke(command.str());
}
bool File::hasSubdirectory(std::string const & subdir)
{
std::stringstream command;
command << "./fs_check -c has_subdir -f " << path_ << " -a " << subdir;
return invoke(command.str());
}
bool File::hasContents(std::string const & contents)
{
std::stringstream command;
command << "./fs_check -c has_contents -f " << path_ << " -a " << contents;
return invoke(command.str());
}
}

View File

@@ -0,0 +1,26 @@
#ifndef WF_TEST_INTEGRATION_FILE_HPP
#define WF_TEST_INTEGRATION_FILE_HPP
#include <string>
namespace webfuse_test
{
class File final
{
public:
explicit File(std::string const& path);
~File();
bool isFile();
bool isDirectory();
bool hasAccessRights(int accessRights);
bool hasSize(size_t size);
bool hasSubdirectory(std::string const & subdir);
bool hasContents(std::string const & contents);
private:
std::string path_;
};
}
#endif

View File

@@ -0,0 +1,288 @@
/* Why this tool is used:
*
* In order to test webfuse as a fuse filesystem, file system operations should be made.
* To check for memory leaks, valgind (memcheck) is used.
*
* Early tests discovered some ugly behavior of valgrind:
* - valgrind intercepts syscalls like open, read and write
* - valgrind does not expect that syscalls are handled within the process to be checked
*
* There is a more or less (un-) documented switch, which changes valgrind's bevahior, but
* this caused other problems.
*
* The second approach used GTests's death tests. Death tests were used quite a while,
* until we discovered a configuration bug when running CTest:
* - memory leaks did not lead to test error
*
* After fixing CTest configuration, memory leaks within the death tests were shown.
* Which is correct, since death tests pematurely exits the program an therefore no
* cleanup is done.
*
* Finally, it was decided to use good old popen together with this tool.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <getopt.h>
struct command;
typedef bool
command_invoke_fn(
struct command * command);
struct command
{
command_invoke_fn * invoke;
char * file;
char * arg;
bool success;
bool has_arg;
};
static bool print_usage(
struct command * command)
{
printf(
"fs_check, (c) 2020 by Webfuse authors (https://github.com/falk-werner/webfuse)\n"
"Checks file information\n"
"\n"
"Usage:\n"
"\t./fs_check --command <command> --file <file> [--arg <arg>]\n"
"\n"
"Options:\n"
"\t-c, --command - Check to perform (see below)\n"
"\t-f, --file - Path to file to check\n"
"\t-a, --arg - Argument, depending on command\n"
"\n"
"Commands:\n"
"\tis_file - checks, if <file> is a regular file; no arg\n"
"\tis_dir - checks, if <file> is a directory; no arg\n"
"\thas_mode - checks, if <file> has the mode given in <arg>\n"
"\thas_size - checks, if <file> has the size given in <arg>\n"
"\thas_subdir - checks, if <file> contains the sub directory given in <arg>\n"
"\thas_contents - checks, if <file> has the contents given in <arg>\n"
);
return command->success;
}
static bool is_file(
struct command * command)
{
struct stat buffer;
int rc = stat(command->file, &buffer);
return ((0 == rc) && (S_ISREG(buffer.st_mode)));
}
static bool is_dir(
struct command * command)
{
struct stat buffer;
int rc = stat(command->file, &buffer);
return ((0 == rc) && (S_ISDIR(buffer.st_mode)));
}
static bool has_mode(
struct command * command)
{
mode_t mode = (mode_t) atoi(command->arg);
struct stat buffer;
int rc = stat(command->file, &buffer);
return ((0 == rc) && (mode == (buffer.st_mode & 0777)));
}
static bool has_size(
struct command * command)
{
int size = atoi(command->arg);
struct stat buffer;
int rc = stat(command->file, &buffer);
return ((0 == rc) && (size == (buffer.st_size)));
}
static bool has_subdir(
struct command * command)
{
bool result = false;
char const * subdir = command->arg;
DIR * dir = opendir(command->file);
if (NULL != dir)
{
struct dirent * entry = readdir(dir);
while (NULL != entry)
{
if (0 == strcmp(subdir, entry->d_name))
{
result = true;
break;
}
entry = readdir(dir);
}
closedir(dir);
}
return result;
}
static bool has_contents(
struct command * command)
{
bool result = false;
char const * contents = command->arg;
size_t length = strlen(contents);
char * buffer = malloc(length);
FILE * file = fopen(command->file, "rb");
{
ssize_t count = fread(buffer, 1, length, file);
fclose(file);
result = (count == (ssize_t) length) && (0 == strncmp(buffer, contents, length));
}
free(buffer);
return result;
}
static bool get_command(
struct command * command,
char const * name)
{
static struct {
char const * name;
command_invoke_fn * invoke;
bool has_arg;
} commands[] =
{
{"is_file" , &is_file , false},
{"is_dir" , &is_dir , false},
{"has_mode" , &has_mode , true},
{"has_size" , &has_size , true},
{"has_subdir" , &has_subdir , true},
{"has_contents", &has_contents, true},
{NULL, NULL, false}
};
for (int i = 0; NULL != commands[i].name; i++)
{
if (0 == strcmp(commands[i].name, name))
{
command->invoke = commands[i].invoke;
command->has_arg = commands[i].has_arg;
return true;
}
}
return false;
}
static void command_init(
struct command * command,
int argc,
char * argv[])
{
static struct option const options[] =
{
{"file" , required_argument, NULL, 'f'},
{"command", required_argument, NULL, 'c'},
{"arg" , required_argument, NULL, 'a'},
{"help" , no_argument , NULL, 'h'},
{NULL, 0, NULL, 0}
};
command->invoke = &print_usage;
command->file = NULL;
command->arg = NULL;
command->success = true;
command->has_arg = false;
optind = 0;
bool is_finished = false;
while (!is_finished)
{
int option_index = 0;
int const c = getopt_long(argc, argv, "f:c:a:h", options, &option_index);
switch(c)
{
case -1:
is_finished = true;
break;
case 'c':
if (!get_command(command, optarg))
{
fprintf(stderr, "error: unknown command\n");
command->invoke = &print_usage;
command->success = false;
is_finished = true;
}
break;
case 'f':
free(command->file);
command->file = strdup(optarg);
break;
case 'a':
free(command->arg);
command->arg = strdup(optarg);
break;
case 'h':
command->invoke = &print_usage;
break;
default:
fprintf(stderr, "error: unknown argument\n");
command->invoke = &print_usage;
command->success = false;
is_finished = true;
break;
}
}
if (command->success)
{
if (NULL == command->file)
{
fprintf(stderr, "error: missing required arg: -f\n");
command->invoke = &print_usage;
command->success = false;
}
else if ((command->has_arg) && (NULL == command->arg))
{
fprintf(stderr, "error: missing required arg: -a\n");
command->invoke = &print_usage;
command->success = false;
}
}
}
static void command_cleanup(
struct command * command)
{
free(command->file);
free(command->arg);
}
int main(int argc, char* argv[])
{
struct command command;
command_init(&command, argc, argv);
bool success = command.invoke(&command);
command_cleanup(&command);
return success ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@@ -0,0 +1,148 @@
#include "webfuse_provider/tests/integration/provider.hpp"
#include "webfuse_provider.h"
#include "webfuse_provider/impl/client.h"
#include <thread>
#include <mutex>
#include <chrono>
#include <string>
#include "webfuse/utils/static_filesystem.h"
using namespace std::chrono_literals;
namespace
{
enum class ConnectionState
{
disconnected,
connected,
connecting
};
}
extern "C"
{
void
webfuse_test_provider_onconnected(
void * user_data)
{
auto * fs = reinterpret_cast<wfp_static_filesystem*>(user_data);
auto * connection_state = reinterpret_cast<ConnectionState*>(wfp_static_filesystem_get_user_data(fs));
*connection_state = ConnectionState::connected;
}
void
webfuse_test_provider_ondisconnected(
void * user_data)
{
auto * fs = reinterpret_cast<wfp_static_filesystem*>(user_data);
auto * connection_state = reinterpret_cast<ConnectionState*>(wfp_static_filesystem_get_user_data(fs));
*connection_state = ConnectionState::disconnected;
}
}
namespace webfuse_test
{
class Provider::Private
{
public:
explicit Private(char const * url)
: is_shutdown_requested(false)
, connection_state(ConnectionState::connecting)
{
config = wfp_client_config_create();
wfp_client_config_set_certpath(config, "client-cert.pem");
wfp_client_config_set_keypath(config, "client-key.pem");
wfp_client_config_set_ca_filepath(config, "server-cert.pem");
wfp_client_config_set_onconnected(config, &webfuse_test_provider_onconnected);
wfp_client_config_set_ondisconnected(config, &webfuse_test_provider_ondisconnected);
fs = wfp_static_filesystem_create(config);
wfp_static_filesystem_set_user_data(fs, reinterpret_cast<void*>(&connection_state));
wfp_static_filesystem_add_text(fs, "hello.txt", 0444, "Hello, World");
client = wfp_client_create(config);
wfp_client_connect(client, url);
while (ConnectionState::connecting == connection_state)
{
wfp_client_service(client);
}
if (ConnectionState::connected == connection_state)
{
thread = std::thread(Run, this);
std::this_thread::sleep_for(200ms);
}
else
{
wfp_client_dispose(client);
wfp_static_filesystem_dispose(fs);
wfp_client_config_dispose(config);
throw std::runtime_error("unable to connect");
}
}
~Private()
{
RequestShutdown();
thread.join();
wfp_client_disconnect(client);
while (ConnectionState::disconnected != connection_state)
{
wfp_client_service(client);
}
wfp_client_dispose(client);
wfp_static_filesystem_dispose(fs);
wfp_client_config_dispose(config);
}
bool IsShutdownRequested()
{
std::lock_guard<std::mutex> lock(shutdown_lock);
return is_shutdown_requested;
}
private:
void RequestShutdown()
{
std::lock_guard<std::mutex> lock(shutdown_lock);
is_shutdown_requested = true;
wfp_client_interrupt(client);
}
static void Run(Provider::Private * context)
{
while (!context->IsShutdownRequested())
{
wfp_client_service(context->client);
}
}
std::mutex shutdown_lock;
std::thread thread;
bool is_shutdown_requested;
ConnectionState connection_state;
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;
}
}

View File

@@ -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

View File

@@ -0,0 +1,154 @@
#include "webfuse_provider/tests/integration/server.hpp"
#include <thread>
#include <mutex>
#include <sstream>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/stat.h>
#include "webfuse_adapter.h"
#include "webfuse/adapter/impl/server.h"
#define WF_PATH_MAX (100)
extern "C"
{
static void webfuse_test_server_cleanup_mountpoint(
void * user_data)
{
char * path = reinterpret_cast<char*>(user_data);
rmdir(path);
free(path);
}
static struct wf_mountpoint *
webfuse_test_server_create_mountpoint(
char const * filesystem,
void * user_data)
{
char const * base_dir = reinterpret_cast<char const*>(user_data);
char path[WF_PATH_MAX];
snprintf(path, WF_PATH_MAX, "%s/%s", base_dir, filesystem);
mkdir(path, 0755);
struct wf_mountpoint * mountpoint = wf_mountpoint_create(path);
wf_mountpoint_set_userdata(
mountpoint,
reinterpret_cast<void*>(strdup(path)),
&webfuse_test_server_cleanup_mountpoint);
return mountpoint;
}
}
namespace webfuse_test
{
class Server::Private
{
public:
Private()
: is_shutdown_requested(false)
{
snprintf(base_dir, WF_PATH_MAX, "%s", "/tmp/webfuse_test_integration_XXXXXX");
char const * result = mkdtemp(base_dir);
if (NULL == result)
{
throw std::runtime_error("unable to create temp dir");
}
config = wf_server_config_create();
wf_server_config_set_port(config, 0);
wf_server_config_set_mountpoint_factory(config,
&webfuse_test_server_create_mountpoint,
reinterpret_cast<void*>(base_dir));
wf_server_config_set_keypath(config, "server-key.pem");
wf_server_config_set_certpath(config, "server-cert.pem");
server = wf_server_create(config);
while (!wf_impl_server_is_operational(server))
{
wf_server_service(server);
}
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<std::mutex> lock(shutdown_lock);
return is_shutdown_requested;
}
std::string GetUrl(void) const
{
int const port = wf_server_get_port(server);
std::ostringstream stream;
stream << "wss://localhost:" << port << "/";
return stream.str();
}
private:
void RequestShutdown()
{
std::lock_guard<std::mutex> lock(shutdown_lock);
is_shutdown_requested = true;
wf_server_interrupt(server);
}
static void Run(Server::Private * context)
{
while (!context->IsShutdownRequested())
{
wf_server_service(context->server);
}
}
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;
}
std::string Server::GetUrl(void) const
{
return d->GetUrl();
}
}

View File

@@ -0,0 +1,25 @@
#ifndef WF_TEST_INTEGRATION_SERVER_HPP
#define WF_TEST_INTEGRATION_SERVER_HPP
#include <string>
namespace webfuse_test
{
class Server
{
public:
Server();
~Server();
void Start(void);
void Stop(void);
char const * GetBaseDir(void) const;
std::string GetUrl(void) const;
private:
class Private;
Private * d;
};
}
#endif

View File

@@ -0,0 +1,97 @@
#include <gtest/gtest.h>
#include "webfuse_provider/tests/integration/server.hpp"
#include "webfuse_provider/tests/integration/provider.hpp"
#include "webfuse_provider/tests/integration/file.hpp"
#include <cstdio>
#include <csignal>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <jansson.h>
#include "webfuse_provider/impl/lws_log.h"
using webfuse_test::Server;
using webfuse_test::Provider;
using webfuse_test::File;
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(server->GetUrl().c_str());
}
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/hello.txt";
File file(file_name);
ASSERT_TRUE(file.isFile());
ASSERT_TRUE(file.hasAccessRights(0444));
ASSERT_TRUE(file.hasSize(12));
}
TEST_F(IntegrationTest, ReadTextFile)
{
std::string file_name = std::string(GetBaseDir()) + "/cprovider/hello.txt";
File file(file_name);
ASSERT_TRUE(file.hasContents("Hello, World"));
}
TEST_F(IntegrationTest, ReadDir)
{
std::string dir_name = std::string(GetBaseDir()) + "/cprovider";
File dir(dir_name);
ASSERT_TRUE(dir.isDirectory());
ASSERT_TRUE(dir.hasSubdirectory("."));
ASSERT_TRUE(dir.hasSubdirectory(".."));
ASSERT_TRUE(dir.hasSubdirectory("hello.txt"));
ASSERT_FALSE(dir.hasSubdirectory("other"));
}

View File

@@ -0,0 +1,112 @@
#include "webfuse_adapter.h"
#include "webfuse_provider.h"
#include <libwebsockets.h>
#include "webfuse/utils/tempdir.hpp"
#include <gtest/gtest.h>
using ::webfuse_test::TempDir;
extern "C"
{
wf_mountpoint *
wf_test_integration_lowlevel_create_mountpoint(
char const *, void * user_data)
{
auto * tempDir = reinterpret_cast<TempDir*>(user_data);
return wf_mountpoint_create(tempDir->path());
}
void
wf_test_integration_lowlevel_on_connected(
void * user_data)
{
int * state = reinterpret_cast<int*>(user_data);
*state = 1;
}
void
wf_test_integration_lowlevel_on_disconnected(
void * user_data)
{
int * state = reinterpret_cast<int*>(user_data);
*state = -1;
}
bool
wf_test_integration_lowlevel_authenticate(
struct wf_credentials const * credentials,
void * )
{
char const * username = wf_credentials_get(credentials, "username");
char const * password = wf_credentials_get(credentials, "password");
return ((0 == strcmp(username, "bob")) && (0 == strcmp(password, "secret")));
}
void
wf_test_integration_lowlevel_get_credentials(
struct wfp_credentials * credentials,
void * )
{
wfp_credentials_set_type(credentials, "username");
wfp_credentials_add(credentials, "username", "bob");
wfp_credentials_add(credentials, "password", "secret");
}
}
TEST(integration, lowlevel)
{
TempDir dir("wf_test");
wf_server_protocol * server_protocol = wf_server_protocol_create(
&wf_test_integration_lowlevel_create_mountpoint,
reinterpret_cast<void*>(&dir));
ASSERT_NE(nullptr, server_protocol);
wf_server_protocol_add_authenticator(server_protocol, "username",
&wf_test_integration_lowlevel_authenticate, nullptr);
int state = 0;
wfp_client_config * client_config = wfp_client_config_create();
ASSERT_NE(nullptr, client_config);
wfp_client_config_set_userdata(client_config, reinterpret_cast<void*>(&state));
wfp_client_config_set_onconnected(client_config, &wf_test_integration_lowlevel_on_connected);
wfp_client_config_set_ondisconnected(client_config, &wf_test_integration_lowlevel_on_disconnected);
wfp_client_config_enable_authentication(client_config, &wf_test_integration_lowlevel_get_credentials);
wfp_client_protocol * client_protocol = wfp_client_protocol_create(client_config);
ASSERT_NE(nullptr, client_protocol);
lws_protocols protocols[3];
memset(protocols, 0, 3 * sizeof(lws_protocols));
wf_server_protocol_init_lws(server_protocol, &protocols[0]);
wfp_client_protocol_init_lws(client_protocol, &protocols[1]);
lws_context_creation_info info;
memset(&info, 0, sizeof(info));
info.port = 8080;
info.protocols = protocols;
info.vhost_name = "localhost";
info.ws_ping_pong_interval = 10;
info.options = LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
struct lws_context * context = lws_create_context(&info);
ASSERT_NE(nullptr, context);
wfp_client_protocol_connect(client_protocol, context, "ws://localhost:8080/");
while (0 == state)
{
lws_service(context, 0);
}
EXPECT_EQ(1, state);
lws_context_destroy(context);
wfp_client_protocol_dispose(client_protocol);
wfp_client_config_dispose(client_config);
wf_server_protocol_dispose(server_protocol);
}