From 7cea9828cb05be7b2e9a40ddca4b15338cbcac2f Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Sun, 5 Apr 2020 20:11:07 +0200 Subject: [PATCH] replaces death tests by popen --- cmake/unit_tests.cmake | 6 +- test/webfuse/tests/integration/file.cc | 85 ++++++ test/webfuse/tests/integration/file.hpp | 26 ++ test/webfuse/tests/integration/fs_check.c | 288 ++++++++++++++++++ .../tests/integration/test_integration.cc | 87 +----- test/webfuse/utils/die_if.cc | 15 - test/webfuse/utils/die_if.hpp | 11 - 7 files changed, 418 insertions(+), 100 deletions(-) create mode 100644 test/webfuse/tests/integration/file.cc create mode 100644 test/webfuse/tests/integration/file.hpp create mode 100644 test/webfuse/tests/integration/fs_check.c delete mode 100644 test/webfuse/utils/die_if.cc delete mode 100644 test/webfuse/utils/die_if.hpp diff --git a/cmake/unit_tests.cmake b/cmake/unit_tests.cmake index 00870d9..60023ee 100644 --- a/cmake/unit_tests.cmake +++ b/cmake/unit_tests.cmake @@ -3,6 +3,10 @@ if(NOT WITHOUT_TESTS AND NOT WITHOUT_ADAPTER AND NOT WITHOUT_PROVIDER) set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --error-exitcode=1") include (CTest) +add_executable(fs_check + test/webfuse/tests/integration/fs_check.c +) + pkg_check_modules(GTEST gtest_main) include(GoogleTest) pkg_check_modules(GMOCK gmock) @@ -21,7 +25,6 @@ add_executable(alltests test/webfuse/tests/core/timer/test_timer.cc test/webfuse/utils/tempdir.cc test/webfuse/utils/file_utils.cc - test/webfuse/utils/die_if.cc test/webfuse/utils/timeout_watcher.cc test/webfuse/utils/path.c test/webfuse/utils/static_filesystem.c @@ -66,6 +69,7 @@ add_executable(alltests test/webfuse/tests/provider/operation/test_readdir.cc test/webfuse/tests/integration/test_lowlevel.cc test/webfuse/tests/integration/test_integration.cc + test/webfuse/tests/integration/file.cc test/webfuse/tests/integration/server.cc test/webfuse/tests/integration/provider.cc ) diff --git a/test/webfuse/tests/integration/file.cc b/test/webfuse/tests/integration/file.cc new file mode 100644 index 0000000..5719382 --- /dev/null +++ b/test/webfuse/tests/integration/file.cc @@ -0,0 +1,85 @@ +#include "webfuse/tests/integration/file.hpp" +#include +#include + +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()); +} + +} \ No newline at end of file diff --git a/test/webfuse/tests/integration/file.hpp b/test/webfuse/tests/integration/file.hpp new file mode 100644 index 0000000..e36e6ce --- /dev/null +++ b/test/webfuse/tests/integration/file.hpp @@ -0,0 +1,26 @@ +#ifndef WF_TEST_INTEGRATION_FILE_HPP +#define WF_TEST_INTEGRATION_FILE_HPP + +#include + +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 diff --git a/test/webfuse/tests/integration/fs_check.c b/test/webfuse/tests/integration/fs_check.c new file mode 100644 index 0000000..55d24c4 --- /dev/null +++ b/test/webfuse/tests/integration/fs_check.c @@ -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 +#include +#include +#include + +#include +#include +#include +#include + +#include + +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 --file [--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 is a regular file; no arg\n" + "\tis_dir - checks, if is a directory; no arg\n" + "\thas_mode - checks, if has the mode given in \n" + "\thas_size - checks, if has the size given in \n" + "\thas_subdir - checks, if contains the sub directory given in \n" + "\thas_contents - checks, if has the contents given in \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; +} \ No newline at end of file diff --git a/test/webfuse/tests/integration/test_integration.cc b/test/webfuse/tests/integration/test_integration.cc index 57c1a91..e7e8215 100644 --- a/test/webfuse/tests/integration/test_integration.cc +++ b/test/webfuse/tests/integration/test_integration.cc @@ -1,6 +1,7 @@ #include #include "webfuse/tests/integration/server.hpp" #include "webfuse/tests/integration/provider.hpp" +#include "webfuse/tests/integration/file.hpp" #include #include @@ -15,11 +16,10 @@ #include #include "webfuse/core/lws_log.h" -#include "webfuse/utils/die_if.hpp" using webfuse_test::Server; using webfuse_test::Provider; -using webfuse_test::die_if; +using webfuse_test::File; namespace { @@ -70,87 +70,28 @@ TEST_F(IntegrationTest, ProvidesTextFile) { std::string file_name = std::string(GetBaseDir()) + "/cprovider/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), ".*"); + 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"; - 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), ".*"); - + File file(file_name); + ASSERT_TRUE(file.hasContents("Hello, World")); } TEST_F(IntegrationTest, ReadDir) { std::string dir_name = std::string(GetBaseDir()) + "/cprovider"; - 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), ".*"); - + 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")); } \ No newline at end of file diff --git a/test/webfuse/utils/die_if.cc b/test/webfuse/utils/die_if.cc deleted file mode 100644 index bd8dd70..0000000 --- a/test/webfuse/utils/die_if.cc +++ /dev/null @@ -1,15 +0,0 @@ -#include "webfuse/utils/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/webfuse/utils/die_if.hpp b/test/webfuse/utils/die_if.hpp deleted file mode 100644 index 95d3ff2..0000000 --- a/test/webfuse/utils/die_if.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef WF_TEST_DIE_IF_HPP -#define WF_TEST_DIE_IF_HPP - -namespace webfuse_test -{ - -extern void die_if(bool expression); - -} - -#endif