diff --git a/CMakeLists.txt b/CMakeLists.txt index 198c11a..ce1b860 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index f4ffb4a..1c544e7 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -34,6 +34,11 @@ public: client.service(); } + void interrupt() + { + client.interrupt(); + } + void set_connection_listener(std::function 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 listener) { d->set_connection_listener(listener); diff --git a/src/webfuse/provider.hpp b/src/webfuse/provider.hpp index 7709504..7c10a8a 100644 --- a/src/webfuse/provider.hpp +++ b/src/webfuse/provider.hpp @@ -20,6 +20,7 @@ public: void set_connection_listener(std::function listener); void connect(std::string const & url); void service(); + void interrupt(); private: class detail; detail * d; diff --git a/src/webfuse/ws/client.cpp b/src/webfuse/ws/client.cpp index 2266e1e..760bcbf 100644 --- a/src/webfuse/ws/client.cpp +++ b/src/webfuse/ws/client.cpp @@ -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; diff --git a/test-src/webfuse/test/daemon.cpp b/test-src/webfuse/test/daemon.cpp new file mode 100644 index 0000000..31d17b2 --- /dev/null +++ b/test-src/webfuse/test/daemon.cpp @@ -0,0 +1,37 @@ +#include "webfuse/test/daemon.hpp" + +#include +#include +#include + +#include + +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); +} + +} \ No newline at end of file diff --git a/test-src/webfuse/test/daemon.hpp b/test-src/webfuse/test/daemon.hpp new file mode 100644 index 0000000..b5827ae --- /dev/null +++ b/test-src/webfuse/test/daemon.hpp @@ -0,0 +1,25 @@ +#ifndef WEBFUSE_DAEMOM_HPP +#define WEBFUSE_DAEMOM_HPP + +#include "webfuse/test/process.hpp" +#include + +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 diff --git a/test-src/webfuse/test/filesystem_mock.hpp b/test-src/webfuse/test/filesystem_mock.hpp new file mode 100644 index 0000000..c7d807c --- /dev/null +++ b/test-src/webfuse/test/filesystem_mock.hpp @@ -0,0 +1,47 @@ +#ifndef WEBFUSE_FILESYSTEM_MOCK_HPP +#define WEBFUSE_FILESYSTEM_MOCK_HPP + +#include "webfuse/filesystem/filesystem_i.hpp" +#include + +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 & entries, uint64_t handle)); + MOCK_METHOD(int, rmdir, (std::string const & path)); + + MOCK_METHOD(int, statfs, (std::string const & path, struct statvfs * statistics)); +}; + +} + +#endif diff --git a/test-src/webfuse/test/fixture.cpp b/test-src/webfuse/test/fixture.cpp new file mode 100644 index 0000000..58ad574 --- /dev/null +++ b/test-src/webfuse/test/fixture.cpp @@ -0,0 +1,63 @@ +#include "webfuse/test/fixture.hpp" +#include +#include +#include +#include + +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(); +} + + +} \ No newline at end of file diff --git a/test-src/webfuse/test/fixture.hpp b/test-src/webfuse/test/fixture.hpp new file mode 100644 index 0000000..7d3e3e5 --- /dev/null +++ b/test-src/webfuse/test/fixture.hpp @@ -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 +#include +#include +#include + +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 shutdown_requested; + std::atomic provider_running; + provider fs_provider; + + tempdir working_dir; + daemon app; + std::thread provider_thread; +}; + + +} + +#endif diff --git a/test-src/webfuse/test/process.cpp b/test-src/webfuse/test/process.cpp new file mode 100644 index 0000000..bd29c5c --- /dev/null +++ b/test-src/webfuse/test/process.cpp @@ -0,0 +1,93 @@ +#include "webfuse/test/process.hpp" + +#include +#include + +#include +#include +#include + +#include + +namespace webfuse +{ + +process::process(std::vector 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(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; +} + +} \ No newline at end of file diff --git a/test-src/webfuse/test/process.hpp b/test-src/webfuse/test/process.hpp new file mode 100644 index 0000000..3154524 --- /dev/null +++ b/test-src/webfuse/test/process.hpp @@ -0,0 +1,29 @@ +#ifndef WEBFUSE_PROCESS_HPP +#define WEBFUSE_PROCESS_HPP + +#include + +#include +#include + +namespace webfuse +{ + +class process +{ + process(process const &) = delete; + process operator=(process const &) = delete; + process(process &&) = delete; + process operator=(process &&) = delete; +public: + process(std::vector const & commandline); + ~process(); + void kill(int signal_number); + int wait(); +private: + pid_t pid; +}; + +} + +#endif diff --git a/test-src/webfuse/test/tempdir.cpp b/test-src/webfuse/test/tempdir.cpp index 9474ba5..760b7dd 100644 --- a/test-src/webfuse/test/tempdir.cpp +++ b/test-src/webfuse/test/tempdir.cpp @@ -15,7 +15,7 @@ tempdir::~tempdir() unlink(path.c_str()); } -std::string const tempdir::name() const +std::string const & tempdir::name() const { return path; } diff --git a/test-src/webfuse/test/tempdir.hpp b/test-src/webfuse/test/tempdir.hpp index 216341b..0e73894 100644 --- a/test-src/webfuse/test/tempdir.hpp +++ b/test-src/webfuse/test/tempdir.hpp @@ -11,7 +11,7 @@ class tempdir public: tempdir(); ~tempdir(); - std::string const name() const; + std::string const & name() const; private: std::string path; diff --git a/test-src/webfuse/test/thread.cpp b/test-src/webfuse/test/thread.cpp deleted file mode 100644 index 2ebef00..0000000 --- a/test-src/webfuse/test/thread.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "webfuse/test/thread.hpp" -#include - -namespace -{ - -extern "C" void * webfuse_thread_main(void * args) -{ - auto * run = reinterpret_cast *>(args); - (*run)(); - return nullptr; -} - -} - - -namespace webfuse -{ - -thread::thread(std::function run) -{ - pthread_create(&real_thread, nullptr, - &webfuse_thread_main, - reinterpret_cast(&run)); - -} - -thread::~thread() -{ - pthread_join(real_thread, nullptr); -} - -void thread::kill(int signal_id) -{ - pthread_kill(real_thread, signal_id); -} - - -} \ No newline at end of file diff --git a/test-src/webfuse/test/thread.hpp b/test-src/webfuse/test/thread.hpp deleted file mode 100644 index 8dfbd67..0000000 --- a/test-src/webfuse/test/thread.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef WEBFUSE_THREAD_HPP -#define WEBFUSE_THREAD_HPP - -#include -#include - -namespace webfuse -{ - -class thread -{ -public: - explicit thread(std::function run); - ~thread(); - void kill(int signal_id); -private: - pthread_t real_thread; -}; - -} - -#endif diff --git a/test-src/webfuse/test_access.cpp b/test-src/webfuse/test_access.cpp new file mode 100644 index 0000000..c1ed4fe --- /dev/null +++ b/test-src/webfuse/test_access.cpp @@ -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 +#include + +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(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); +} diff --git a/test-src/webfuse/test_app.cpp b/test-src/webfuse/test_app.cpp index 0f546c6..77d4f56 100644 --- a/test-src/webfuse/test_app.cpp +++ b/test-src/webfuse/test_app.cpp @@ -1,38 +1,7 @@ #include "webfuse/webfuse.hpp" -#include "webfuse/test/thread.hpp" -#include "webfuse/test/tempdir.hpp" #include -#include -#include -#include -#include -#include - -extern "C" void * run(void * args) -{ - webfuse::app * app = reinterpret_cast(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); -} \ No newline at end of file