1
0
mirror of https://github.com/falk-werner/webfuse synced 2024-10-27 20:34:10 +00:00

added basic access test

This commit is contained in:
Falk Werner 2022-12-31 20:41:45 +01:00
parent a86061356a
commit 2e3d7a66b5
17 changed files with 403 additions and 95 deletions

View File

@ -50,11 +50,14 @@ if(NOT(WITHOUT_TEST))
pkg_check_modules(GMOCK REQUIRED gmock)
add_executable(alltests
test-src/webfuse/test/thread.cpp
test-src/webfuse/test/tempdir.cpp
test-src/webfuse/test/fixture.cpp
test-src/webfuse/test/process.cpp
test-src/webfuse/test/daemon.cpp
test-src/webfuse/test_app.cpp
test-src/webfuse/test_request_type.cpp
test-src/webfuse/test_response_type.cpp
test-src/webfuse/test_access.cpp
test-src/webfuse/filesystem/test_status.cpp
test-src/webfuse/filesystem/test_accessmode.cpp
test-src/webfuse/filesystem/test_openflags.cpp

View File

@ -34,6 +34,11 @@ public:
client.service();
}
void interrupt()
{
client.interrupt();
}
void set_connection_listener(std::function<void(bool)> listener)
{
client.set_connection_listener(listener);
@ -144,6 +149,11 @@ void provider::service()
d->service();
}
void provider::interrupt()
{
d->interrupt();
}
void provider::set_connection_listener(std::function<void(bool)> listener)
{
d->set_connection_listener(listener);

View File

@ -20,6 +20,7 @@ public:
void set_connection_listener(std::function<void(bool)> listener);
void connect(std::string const & url);
void service();
void interrupt();
private:
class detail;
detail * d;

View File

@ -34,6 +34,7 @@ extern "C" int webfuse_client_callback(lws * wsi, lws_callback_reasons reason, v
context->connection_listener(true);
break;
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
std::cout << "connection error" << std::endl;
// fall-through
case LWS_CALLBACK_CLIENT_CLOSED:
std::cout << "closed" << std::endl;

View File

@ -0,0 +1,37 @@
#include "webfuse/test/daemon.hpp"
#include <limits.h>
#include <unistd.h>
#include <libgen.h>
#include <csignal>
namespace
{
std::string get_executable_path()
{
char directory[PATH_MAX] = { '\0' };
readlink("/proc/self/exe", directory, PATH_MAX);
dirname(directory);
return std::string(directory) + "/webfuse";
}
}
namespace webfuse
{
daemon::daemon(std::string const & mountpoint)
: p({get_executable_path(), "-f", mountpoint})
{
}
daemon::~daemon()
{
p.kill(SIGINT);
}
}

View File

@ -0,0 +1,25 @@
#ifndef WEBFUSE_DAEMOM_HPP
#define WEBFUSE_DAEMOM_HPP
#include "webfuse/test/process.hpp"
#include <string>
namespace webfuse
{
class daemon
{
daemon(daemon const &) = delete;
daemon& operator=(daemon const &) = delete;
daemon(daemon &&) = delete;
daemon& operator=(daemon &&) = delete;
public:
explicit daemon(std::string const & mountpoint);
~daemon();
private:
process p;
};
}
#endif

View File

@ -0,0 +1,47 @@
#ifndef WEBFUSE_FILESYSTEM_MOCK_HPP
#define WEBFUSE_FILESYSTEM_MOCK_HPP
#include "webfuse/filesystem/filesystem_i.hpp"
#include <gmock/gmock.h>
namespace webfuse
{
class filesystem_mock: public filesystem_i
{
public:
~filesystem_mock() override = default;
MOCK_METHOD(int, access, (std::string const & path, int mode));
MOCK_METHOD(int, getattr, (std::string const & path, struct stat * attr));
MOCK_METHOD(int, readlink, (std::string const & path, std::string & out));
MOCK_METHOD(int, symlink, (std::string const & target, std::string const & linkpath));
MOCK_METHOD(int, link, (std::string const & old_path, std::string const & new_path));
MOCK_METHOD(int, rename, (std::string const & old_path, std::string const & new_path, int flags));
MOCK_METHOD(int, chmod, (std::string const & path, mode_t mode));
MOCK_METHOD(int, chown, (std::string const & path, uid_t uid, gid_t gid));
MOCK_METHOD(int, truncate, (std::string const & path, uint64_t size, uint64_t handle));
MOCK_METHOD(int, fsync, (std::string const & path, bool is_datasync, uint64_t handle));
MOCK_METHOD(int, utimens, (std::string const &path, struct timespec tv[2], uint64_t handle));
MOCK_METHOD(int, open, (std::string const & path, int flags, uint64_t & handle));
MOCK_METHOD(int, mknod, (std::string const & path, mode_t mode, dev_t rdev));
MOCK_METHOD(int, create, (std::string const & path, mode_t mode, uint64_t & handle));
MOCK_METHOD(int, release, (std::string const & path, uint64_t handle));
MOCK_METHOD(int, unlink, (std::string const & path));
MOCK_METHOD(int, read, (std::string const & path, char * buffer, size_t buffer_size, uint64_t offset, uint64_t handle));
MOCK_METHOD(int, write, (std::string const & path, char const * buffer, size_t buffer_size, uint64_t offset, uint64_t handle));
MOCK_METHOD(int, mkdir, (std::string const & path, mode_t mode));
MOCK_METHOD(int, readdir, (std::string const & path, std::vector<std::string> & entries, uint64_t handle));
MOCK_METHOD(int, rmdir, (std::string const & path));
MOCK_METHOD(int, statfs, (std::string const & path, struct statvfs * statistics));
};
}
#endif

View File

@ -0,0 +1,63 @@
#include "webfuse/test/fixture.hpp"
#include <csignal>
#include <cstring>
#include <chrono>
#include <iostream>
namespace webfuse
{
fixture::fixture(filesystem_i & fs)
: shutdown_requested(false)
, provider_running(false)
, fs_provider(fs)
, app(working_dir.name())
{
fs_provider.set_connection_listener([this](bool is_connected) {
if (is_connected)
{
this->provider_running = true;
}
if ((!is_connected) && (!this->provider_running))
{
this->reconnect();
}
});
provider_thread = std::thread(std::bind(&fixture::provider_run, this));
while (!provider_running)
{
std::this_thread::yield();
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
fixture::~fixture()
{
shutdown_requested = true;
fs_provider.interrupt();
provider_thread.join();
}
void fixture::provider_run()
{
fs_provider.connect("ws://localhost:8081/");
while (!shutdown_requested)
{
fs_provider.service();
}
}
void fixture::reconnect()
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
fs_provider.connect("ws://localhost:8081/");
}
std::string const & fixture::get_path() const
{
return working_dir.name();
}
}

View File

@ -0,0 +1,46 @@
#ifndef WEBFUSE_FIXTURE_HPP
#define WEBFUSE_FIXTURE_HPP
#include "webfuse/filesystem/filesystem_i.hpp"
#include "webfuse/webfuse.hpp"
#include "webfuse/provider.hpp"
#include "webfuse/test/tempdir.hpp"
#include "webfuse/test/daemon.hpp"
#include <string>
#include <memory>
#include <thread>
#include <atomic>
namespace webfuse
{
class fixture
{
fixture(fixture const &) = delete;
fixture& operator=(fixture const &) = delete;
fixture(fixture const &&) = delete;
fixture& operator=(fixture &&) = delete;
public:
explicit fixture(filesystem_i & fs);
~fixture();
std::string const & get_path() const;
void reconnect();
private:
void provider_run();
std::atomic<bool> shutdown_requested;
std::atomic<bool> provider_running;
provider fs_provider;
tempdir working_dir;
daemon app;
std::thread provider_thread;
};
}
#endif

View File

@ -0,0 +1,93 @@
#include "webfuse/test/process.hpp"
#include <sys/wait.h>
#include <fcntl.h>
#include <csignal>
#include <cstring>
#include <cstdlib>
#include <stdexcept>
namespace webfuse
{
process::process(std::vector<std::string> const & commandline)
{
if (commandline.empty())
{
throw std::runtime_error("missing command");
}
pid = fork();
if (pid == 0)
{
size_t const count = commandline.size() + 1;
char ** args = reinterpret_cast<char**>(malloc(sizeof(char*) * count));
args[count - 1] = nullptr;
for(size_t i = 0; i < commandline.size(); i++)
{
args[i] = strdup(commandline[i].c_str());
}
closefrom(0);
open("/dev/null", O_RDONLY);
open("/dev/null", O_WRONLY);
dup2(STDOUT_FILENO, STDERR_FILENO);
execv(args[0], args);
// this should not be reached
for(size_t i = 0; i < count; i++)
{
free(args[i]);
}
free(args);
exit(EXIT_FAILURE);
}
else if (pid > 0)
{
// parent: do nothing
}
else
{
throw std::runtime_error("failed to fork");
}
}
process::~process()
{
if (pid > 0)
{
wait();
}
}
void process::kill(int signal_number)
{
if (pid > 0)
{
::kill(pid, signal_number);
}
}
int process::wait()
{
int exit_code = -1;
if (pid > 0)
{
int status = 0;
int rc = waitpid(pid, &status, 0);
if (rc == 0)
{
exit_code = WEXITSTATUS(status);
pid = 0;
}
}
return exit_code;
}
}

View File

@ -0,0 +1,29 @@
#ifndef WEBFUSE_PROCESS_HPP
#define WEBFUSE_PROCESS_HPP
#include <unistd.h>
#include <string>
#include <vector>
namespace webfuse
{
class process
{
process(process const &) = delete;
process operator=(process const &) = delete;
process(process &&) = delete;
process operator=(process &&) = delete;
public:
process(std::vector<std::string> const & commandline);
~process();
void kill(int signal_number);
int wait();
private:
pid_t pid;
};
}
#endif

View File

@ -15,7 +15,7 @@ tempdir::~tempdir()
unlink(path.c_str());
}
std::string const tempdir::name() const
std::string const & tempdir::name() const
{
return path;
}

View File

@ -11,7 +11,7 @@ class tempdir
public:
tempdir();
~tempdir();
std::string const name() const;
std::string const & name() const;
private:
std::string path;

View File

@ -1,39 +0,0 @@
#include "webfuse/test/thread.hpp"
#include <csignal>
namespace
{
extern "C" void * webfuse_thread_main(void * args)
{
auto * run = reinterpret_cast<std::function<void(void)> *>(args);
(*run)();
return nullptr;
}
}
namespace webfuse
{
thread::thread(std::function<void(void)> run)
{
pthread_create(&real_thread, nullptr,
&webfuse_thread_main,
reinterpret_cast<void*>(&run));
}
thread::~thread()
{
pthread_join(real_thread, nullptr);
}
void thread::kill(int signal_id)
{
pthread_kill(real_thread, signal_id);
}
}

View File

@ -1,22 +0,0 @@
#ifndef WEBFUSE_THREAD_HPP
#define WEBFUSE_THREAD_HPP
#include <pthread.h>
#include <functional>
namespace webfuse
{
class thread
{
public:
explicit thread(std::function<void(void)> run);
~thread();
void kill(int signal_id);
private:
pthread_t real_thread;
};
}
#endif

View File

@ -0,0 +1,45 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/fixture.hpp"
#include "webfuse/test/filesystem_mock.hpp"
#include "webfuse/test/daemon.hpp"
#include <gtest/gtest.h>
#include <unistd.h>
using testing::_;
using testing::Return;
using testing::Invoke;
using testing::AnyNumber;
namespace
{
int fs_getattr (std::string const & path, struct stat * attr)
{
memset(reinterpret_cast<void*>(attr),0, sizeof(struct stat));
if (path == "/foo")
{
attr->st_nlink = 0;
attr->st_mode = S_IFREG | 0755;
return 0;
}
else
{
return -ENOENT;
}
}
}
TEST(access, ok)
{
webfuse::filesystem_mock fs;
webfuse::fixture fixture(fs);
std::cout << "setup" << std::endl;
EXPECT_CALL(fs, access("/foo",_)).WillOnce(Return(0));
EXPECT_CALL(fs, getattr(_,_)).WillRepeatedly(Invoke(fs_getattr));
auto const path = fixture.get_path() + "/foo";
int const rc = ::access(path.c_str(), F_OK);
ASSERT_EQ(0, rc);
}

View File

@ -1,38 +1,7 @@
#include "webfuse/webfuse.hpp"
#include "webfuse/test/thread.hpp"
#include "webfuse/test/tempdir.hpp"
#include <gtest/gtest.h>
#include <csignal>
#include <cstring>
#include <cstdlib>
#include <chrono>
#include <thread>
extern "C" void * run(void * args)
{
webfuse::app * app = reinterpret_cast<webfuse::app*>(args);
return nullptr;
}
TEST(app, init)
{
webfuse::app app;
}
TEST(app, run)
{
webfuse::tempdir dir;
webfuse::thread thread([&dir](){
webfuse::app app;
char arg0[] = "webfuse";
char arg1[] = "-f";
char* arg2 = strdup(dir.name().c_str());
char* argv[] = { arg0, arg1, arg2, nullptr};
int rc = app.run(3, argv);
free(arg2);
});
std::this_thread::sleep_for(std::chrono::seconds(1));
thread.kill(SIGINT);
}