#include "configpaths.h" #include #include #include "3rd-party/catch.hpp" #include "test_helpers/chmod.h" #include "test_helpers/envvar.h" #include "test_helpers/misc.h" #include "test_helpers/opts.h" #include "test_helpers/tempdir.h" #include "utils.h" using namespace newsboat; TEST_CASE("ConfigPaths returns paths to Newsboat dotdir if no Newsboat dirs " "exist", "[ConfigPaths]") { test_helpers::TempDir tmp; const auto newsboat_dir = tmp.get_path() + ".newsboat"; REQUIRE(0 == utils::mkdir_parents(newsboat_dir, 0700)); test_helpers::EnvVar home("HOME"); home.set(tmp.get_path()); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); ConfigPaths paths; REQUIRE(paths.initialized()); REQUIRE(paths.url_file() == newsboat_dir + "/urls"); REQUIRE(paths.cache_file() == newsboat_dir + "/cache.db"); REQUIRE(paths.lock_file() == newsboat_dir + "/cache.db.lock"); REQUIRE(paths.config_file() == newsboat_dir + "/config"); REQUIRE(paths.queue_file() == newsboat_dir + "/queue"); REQUIRE(paths.search_history_file() == newsboat_dir + "/history.search"); REQUIRE(paths.cmdline_history_file() == newsboat_dir + "/history.cmdline"); } TEST_CASE("ConfigPaths returns paths to Newsboat XDG dirs if they exist and " "the dotdir doesn't", "[ConfigPaths]") { test_helpers::TempDir tmp; const auto config_dir = tmp.get_path() + ".config/newsboat"; REQUIRE(0 == utils::mkdir_parents(config_dir, 0700)); const auto data_dir = tmp.get_path() + ".local/share/newsboat"; REQUIRE(0 == utils::mkdir_parents(data_dir, 0700)); test_helpers::EnvVar home("HOME"); home.set(tmp.get_path()); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); const auto check = [&]() { ConfigPaths paths; REQUIRE(paths.initialized()); REQUIRE(paths.config_file() == config_dir + "/config"); REQUIRE(paths.url_file() == config_dir + "/urls"); REQUIRE(paths.cache_file() == data_dir + "/cache.db"); REQUIRE(paths.lock_file() == data_dir + "/cache.db.lock"); REQUIRE(paths.queue_file() == data_dir + "/queue"); REQUIRE(paths.search_history_file() == data_dir + "/history.search"); REQUIRE(paths.cmdline_history_file() == data_dir + "/history.cmdline"); }; SECTION("XDG_CONFIG_HOME is set") { test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.set(tmp.get_path() + ".config"); SECTION("XDG_DATA_HOME is set") { test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.set(tmp.get_path() + ".local/share"); check(); } SECTION("XDG_DATA_HOME is not set") { test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); check(); } } SECTION("XDG_CONFIG_HOME is not set") { test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); SECTION("XDG_DATA_HOME is set") { test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.set(tmp.get_path() + ".local/share"); check(); } SECTION("XDG_DATA_HOME is not set") { test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); check(); } } } TEST_CASE("ConfigPaths::process_args replaces paths with the ones supplied by " "CliArgsParser", "[ConfigPaths]") { // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar home("HOME"); home.unset(); test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); const auto url_file = std::string("my urls file"); const auto cache_file = std::string("/path/to/cache file.db"); const auto lock_file = cache_file + ".lock"; const auto config_file = std::string("this is a/config"); test_helpers::Opts opts({ "newsboat", "-u", url_file, "-c", cache_file, "-C", config_file, "-q"}); CliArgsParser parser(opts.argc(), opts.argv()); ConfigPaths paths; REQUIRE(paths.initialized()); paths.process_args(parser); REQUIRE(paths.url_file() == url_file); REQUIRE(paths.cache_file() == cache_file); REQUIRE(paths.lock_file() == lock_file); REQUIRE(paths.config_file() == config_file); } TEST_CASE("ConfigPaths::set_cache_file changes paths to cache and lock files", "[ConfigPaths]") { const auto test_dir = std::string("some/dir/we/use/as/home"); const auto newsboat_dir = test_dir + "/.newsboat/"; test_helpers::EnvVar home("HOME"); home.set(test_dir); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); ConfigPaths paths; REQUIRE(paths.initialized()); REQUIRE(paths.cache_file() == newsboat_dir + "cache.db"); REQUIRE(paths.lock_file() == newsboat_dir + "cache.db.lock"); const auto new_cache = std::string("something/entirely different.sqlite3"); paths.set_cache_file(new_cache); REQUIRE(paths.cache_file() == new_cache); REQUIRE(paths.lock_file() == new_cache + ".lock"); } TEST_CASE("ConfigPaths::create_dirs() returns true if both config and data dirs " "were successfully created or already existed", "[ConfigPaths]") { test_helpers::TempDir tmp; test_helpers::EnvVar home("HOME"); home.set(tmp.get_path()); INFO("Temporary directory (used as HOME): " << tmp.get_path()); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); const auto require_exists = [&tmp](std::vector dirs) { ConfigPaths paths; REQUIRE(paths.initialized()); REQUIRE(test_helpers::starts_with(tmp.get_path(), paths.url_file())); REQUIRE(test_helpers::starts_with(tmp.get_path(), paths.config_file())); REQUIRE(test_helpers::starts_with(tmp.get_path(), paths.cache_file())); REQUIRE(test_helpers::starts_with(tmp.get_path(), paths.lock_file())); REQUIRE(test_helpers::starts_with(tmp.get_path(), paths.queue_file())); REQUIRE(test_helpers::starts_with(tmp.get_path(), paths.search_history_file())); REQUIRE(test_helpers::starts_with(tmp.get_path(), paths.cmdline_history_file())); REQUIRE(paths.create_dirs()); for (const auto& dir : dirs) { INFO("Checking directory " << dir); const auto dir_exists = 0 == ::access(dir.c_str(), R_OK | X_OK); REQUIRE(dir_exists); } }; const auto dotdir = tmp.get_path() + ".newsboat/"; const auto default_config_dir = tmp.get_path() + ".config/newsboat/"; const auto default_data_dir = tmp.get_path() + ".local/share/newsboat/"; SECTION("Using dotdir") { SECTION("Dotdir didn't exist") { require_exists({dotdir}); } SECTION("Dotdir already existed") { INFO("Creating " << dotdir); REQUIRE(utils::mkdir_parents(dotdir, 0700) == 0); require_exists({dotdir}); } } SECTION("Using XDG dirs") { SECTION("No XDG environment variables") { test_helpers::EnvVar xdg_config_home("XDG_CONFIG_HOME"); xdg_config_home.unset(); test_helpers::EnvVar xdg_data_home("XDG_DATA_HOME"); xdg_data_home.unset(); SECTION("No dirs existed => dotdir created") { require_exists({dotdir}); } SECTION("Config dir existed, data dir didn't => data dir created") { INFO("Creating " << default_config_dir); REQUIRE(utils::mkdir_parents(default_config_dir, 0700) == 0); require_exists({default_config_dir, default_data_dir}); } SECTION("Data dir existed, config dir didn't => dotdir created") { INFO("Creating " << default_data_dir); REQUIRE(utils::mkdir_parents(default_data_dir, 0700) == 0); require_exists({dotdir}); } SECTION("Both config and data dir existed => just returns true") { INFO("Creating " << default_config_dir); REQUIRE(utils::mkdir_parents(default_config_dir, 0700) == 0); INFO("Creating " << default_data_dir); REQUIRE(utils::mkdir_parents(default_data_dir, 0700) == 0); require_exists({default_config_dir, default_data_dir}); } } SECTION("XDG_CONFIG_HOME redefined") { const auto config_home = tmp.get_path() + "config" + std::to_string(rand()) + "/"; INFO("Config home is " << config_home); const auto config_dir = config_home + "/newsboat"; test_helpers::EnvVar xdg_config_home("XDG_CONFIG_HOME"); xdg_config_home.set(config_home); test_helpers::EnvVar xdg_data_home("XDG_DATA_HOME"); xdg_data_home.unset(); SECTION("No dirs existed => dotdir created") { require_exists({dotdir}); } SECTION("Config dir existed, data dir didn't => data dir created") { INFO("Creating " << config_dir); REQUIRE(utils::mkdir_parents(config_dir, 0700) == 0); require_exists({config_dir, default_data_dir}); } SECTION("Data dir existed, config dir didn't => dotdir created") { INFO("Creating " << default_data_dir); REQUIRE(utils::mkdir_parents(default_data_dir, 0700) == 0); require_exists({dotdir}); } SECTION("Both config and data dir existed => just returns true") { INFO("Creating " << config_dir); REQUIRE(utils::mkdir_parents(config_dir, 0700) == 0); INFO("Creating " << default_data_dir); REQUIRE(utils::mkdir_parents(default_data_dir, 0700) == 0); require_exists({config_dir, default_data_dir}); } } SECTION("XDG_DATA_HOME redefined") { test_helpers::EnvVar xdg_config_home("XDG_CONFIG_HOME"); xdg_config_home.unset(); const auto data_home = tmp.get_path() + "data" + std::to_string(rand()) + "/"; INFO("Data home is " << data_home); const auto data_dir = data_home + "/newsboat"; test_helpers::EnvVar xdg_data_home("XDG_DATA_HOME"); xdg_data_home.set(data_home); SECTION("No dirs existed => dotdir created") { require_exists({dotdir}); } SECTION("Config dir existed, data dir didn't => data dir created") { INFO("Creating " << default_config_dir); REQUIRE(utils::mkdir_parents(default_config_dir, 0700) == 0); require_exists({default_config_dir, data_dir}); } SECTION("Data dir existed, config dir didn't => dotdir created") { INFO("Creating " << data_dir); REQUIRE(utils::mkdir_parents(data_dir, 0700) == 0); require_exists({dotdir}); } SECTION("Both config and data dir existed => just returns true") { INFO("Creating " << default_config_dir); REQUIRE(utils::mkdir_parents(default_config_dir, 0700) == 0); INFO("Creating " << data_dir); REQUIRE(utils::mkdir_parents(data_dir, 0700) == 0); require_exists({default_config_dir, data_dir}); } } SECTION("Both XDG_CONFIG_HOME and XDG_DATA_HOME redefined") { const auto config_home = tmp.get_path() + "config" + std::to_string(rand()) + "/"; INFO("Config home is " << config_home); const auto config_dir = config_home + "/newsboat"; const auto data_home = tmp.get_path() + "data" + std::to_string(rand()) + "/"; INFO("Data home is " << data_home); const auto data_dir = data_home + "/newsboat"; test_helpers::EnvVar xdg_config_home("XDG_CONFIG_HOME"); xdg_config_home.set(config_home); test_helpers::EnvVar xdg_data_home("XDG_DATA_HOME"); xdg_data_home.set(data_home); SECTION("No dirs existed => dotdir created") { require_exists({dotdir}); } SECTION("Config dir existed, data dir didn't => data dir created") { INFO("Creating " << config_dir); REQUIRE(utils::mkdir_parents(config_dir, 0700) == 0); require_exists({config_dir, data_dir}); } SECTION("Data dir existed, config dir didn't => dotdir created") { INFO("Creating " << data_dir); REQUIRE(utils::mkdir_parents(data_dir, 0700) == 0); require_exists({dotdir}); } SECTION("Both config and data dir existed => just returns true") { INFO("Creating " << config_dir); REQUIRE(utils::mkdir_parents(config_dir, 0700) == 0); INFO("Creating " << data_dir); REQUIRE(utils::mkdir_parents(data_dir, 0700) == 0); require_exists({config_dir, data_dir}); } } } } /// Creates a file at given `filepath` and writes `content` into it. /// /// Returns `true` if successful, `false` otherwise. bool create_file(const std::string& filepath, const std::string& contents) { std::ofstream out(filepath, std::ios::trunc); if (!out.is_open()) { return false; } out << contents; out.close(); return !out.fail(); } /// Strings that are placed in files before running commands, to see if /// commands modify those files. struct FileSentries { std::string config = std::to_string(rand()) + "config"; std::string urls = std::to_string(rand()) + "urls"; std::string cache = std::to_string(rand()) + "cache"; std::string queue = std::to_string(rand()) + "queue"; std::string search = std::to_string(rand()) + "search"; std::string cmdline = std::to_string(rand()) + "cmdline"; }; void mock_newsbeuter_dotdir( const test_helpers::TempDir& tmp, const FileSentries& sentries) { const auto dotdir_path = tmp.get_path() + ".newsbeuter/"; REQUIRE(::mkdir(dotdir_path.c_str(), 0700) == 0); REQUIRE(create_file(dotdir_path + "config", sentries.config)); REQUIRE(create_file(dotdir_path + "urls", sentries.urls)); REQUIRE(create_file(dotdir_path + "cache.db", sentries.cache)); REQUIRE(create_file(dotdir_path + "queue", sentries.queue)); REQUIRE(create_file(dotdir_path + "history.search", sentries.search)); REQUIRE(create_file(dotdir_path + "history.cmdline", sentries.cmdline)); } void mock_newsbeuter_xdg_dirs( const std::string& config_dir_path, const std::string& data_dir_path, const FileSentries& sentries) { REQUIRE(utils::mkdir_parents(config_dir_path, 0700) == 0); REQUIRE(create_file(config_dir_path + "config", sentries.config)); REQUIRE(create_file(config_dir_path + "urls", sentries.urls)); REQUIRE(utils::mkdir_parents(data_dir_path, 0700) == 0); REQUIRE(create_file(data_dir_path + "cache.db", sentries.cache)); REQUIRE(create_file(data_dir_path + "queue", sentries.queue)); REQUIRE(create_file(data_dir_path + "history.search", sentries.search)); REQUIRE(create_file(data_dir_path + "history.cmdline", sentries.cmdline)); } void mock_newsbeuter_xdg_dirs( const test_helpers::TempDir& tmp, const FileSentries& sentries) { const auto config_dir_path = tmp.get_path() + ".config/newsbeuter/"; const auto data_dir_path = tmp.get_path() + ".local/share/newsbeuter/"; mock_newsbeuter_xdg_dirs(config_dir_path, data_dir_path, sentries); } void mock_newsboat_dotdir( const test_helpers::TempDir& tmp, const FileSentries& sentries) { const auto dotdir_path = tmp.get_path() + ".newsboat/"; REQUIRE(::mkdir(dotdir_path.c_str(), 0700) == 0); const auto urls_file = dotdir_path + "urls"; REQUIRE(create_file(urls_file, sentries.urls)); } void mock_newsboat_xdg_dirs( const std::string& config_dir_path, const std::string& /*data_dir_path*/, const FileSentries& sentries) { REQUIRE(utils::mkdir_parents(config_dir_path, 0700) == 0); const auto urls_file = config_dir_path + "urls"; REQUIRE(create_file(urls_file, sentries.urls)); } void mock_newsboat_xdg_dirs( const test_helpers::TempDir& tmp, const FileSentries& sentries) { const auto config_dir_path = tmp.get_path() + ".config/newsboat/"; const auto data_dir_path = tmp.get_path() + ".local/share/newsboat/"; mock_newsboat_xdg_dirs(config_dir_path, data_dir_path, sentries); } TEST_CASE("try_migrate_from_newsbeuter() doesn't migrate if config paths " "were specified on the command line", "[ConfigPaths]") { test_helpers::TempDir tmp; test_helpers::EnvVar home("HOME"); home.set(tmp.get_path()); INFO("Temporary directory (used as HOME): " << tmp.get_path()); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); FileSentries beuter_sentries; const auto check = [&]() { FileSentries boat_sentries; const auto url_file = tmp.get_path() + "my urls file"; REQUIRE(create_file(url_file, boat_sentries.urls)); const auto cache_file = tmp.get_path() + "new cache.db"; REQUIRE(create_file(cache_file, boat_sentries.cache)); const auto config_file = tmp.get_path() + "custom config file"; REQUIRE(create_file(config_file, boat_sentries.config)); test_helpers::Opts opts({ "newsboat", "-u", url_file, "-c", cache_file, "-C", config_file, "-q"}); CliArgsParser parser(opts.argc(), opts.argv()); ConfigPaths paths; REQUIRE(paths.initialized()); paths.process_args(parser); // No migration should occur, so should return false. REQUIRE_FALSE(paths.try_migrate_from_newsbeuter()); INFO("Newsbeuter's urls file sentry: " << beuter_sentries.urls); REQUIRE(test_helpers::file_contents(url_file).at(0) == boat_sentries.urls); INFO("Newsbeuter's config file sentry: " << beuter_sentries.config); REQUIRE(test_helpers::file_contents(config_file).at(0) == boat_sentries.config); INFO("Newsbeuter's cache file sentry: " << beuter_sentries.cache); REQUIRE(test_helpers::file_contents(cache_file).at(0) == boat_sentries.cache); }; SECTION("Newsbeuter dotdir exists") { mock_newsbeuter_dotdir(tmp, beuter_sentries); check(); } SECTION("Newsbeuter XDG dirs exist") { mock_newsbeuter_xdg_dirs(tmp, beuter_sentries); check(); } } TEST_CASE("try_migrate_from_newsbeuter() doesn't migrate if urls file " "already exists", "[ConfigPaths]") { test_helpers::TempDir tmp; test_helpers::EnvVar home("HOME"); home.set(tmp.get_path()); INFO("Temporary directory (used as HOME): " << tmp.get_path()); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); FileSentries beuter_sentries; FileSentries boat_sentries; const auto check = [&](const std::string& urls_filepath) { ConfigPaths paths; REQUIRE(paths.initialized()); // No migration should occur, so should return false. REQUIRE_FALSE(paths.try_migrate_from_newsbeuter()); INFO("Newsbeuter's urls file sentry: " << beuter_sentries.urls); REQUIRE(test_helpers::file_contents(urls_filepath).at(0) == boat_sentries.urls); }; SECTION("Newsbeuter dotdir exists") { mock_newsbeuter_dotdir(tmp, beuter_sentries); SECTION("Newsboat uses dotdir") { mock_newsboat_dotdir(tmp, boat_sentries); check(tmp.get_path() + ".newsboat/urls"); } SECTION("Newsboat uses XDG") { SECTION("Default XDG locations") { mock_newsboat_xdg_dirs(tmp, boat_sentries); check(tmp.get_path() + ".config/newsboat/urls"); } SECTION("XDG_CONFIG_HOME redefined") { const auto config_dir = tmp.get_path() + "xdg-conf/"; REQUIRE(::mkdir(config_dir.c_str(), 0700) == 0); xdg_config.set(config_dir); const auto newsboat_config_dir = config_dir + "newsboat/"; mock_newsboat_xdg_dirs( newsboat_config_dir, tmp.get_path() + ".local/share/newsboat/", boat_sentries); check(newsboat_config_dir + "urls"); } SECTION("XDG_DATA_HOME redefined") { const auto data_dir = tmp.get_path() + "xdg-data/"; REQUIRE(::mkdir(data_dir.c_str(), 0700) == 0); xdg_data.set(data_dir); const auto newsboat_config_dir = tmp.get_path() + ".config/newsboat/"; const auto newsboat_data_dir = data_dir + "newsboat/"; mock_newsboat_xdg_dirs( newsboat_config_dir, newsboat_data_dir, boat_sentries); check(newsboat_config_dir + "urls"); } SECTION("Both XDG_CONFIG_HOME and XDG_DATA_HOME redefined") { const auto config_dir = tmp.get_path() + "xdg-conf/"; REQUIRE(::mkdir(config_dir.c_str(), 0700) == 0); xdg_config.set(config_dir); const auto data_dir = tmp.get_path() + "xdg-data/"; REQUIRE(::mkdir(data_dir.c_str(), 0700) == 0); xdg_data.set(data_dir); const auto newsboat_config_dir = config_dir + "newsboat/"; const auto newsboat_data_dir = data_dir + "newsboat/"; mock_newsboat_xdg_dirs( newsboat_config_dir, newsboat_data_dir, boat_sentries); check(newsboat_config_dir + "urls"); } } } SECTION("Newsbeuter XDG dirs exist") { SECTION("Default XDG locations") { mock_newsbeuter_xdg_dirs(tmp, beuter_sentries); SECTION("Newsboat uses dotdir") { mock_newsboat_dotdir(tmp, boat_sentries); check(tmp.get_path() + ".newsboat/urls"); } SECTION("Newsboat uses XDG") { mock_newsboat_xdg_dirs(tmp, boat_sentries); check(tmp.get_path() + ".config/newsboat/urls"); } } SECTION("XDG_CONFIG_HOME redefined") { const auto config_dir = tmp.get_path() + "xdg-conf/"; REQUIRE(::mkdir(config_dir.c_str(), 0700) == 0); xdg_config.set(config_dir); mock_newsbeuter_xdg_dirs( config_dir + "newsbeuter/", tmp.get_path() + ".local/share/newsbeuter/", beuter_sentries); SECTION("Newsboat uses dotdir") { mock_newsboat_dotdir(tmp, boat_sentries); check(tmp.get_path() + ".newsboat/urls"); } SECTION("Newsboat uses XDG") { const auto newsboat_config_dir = config_dir + "newsboat/"; mock_newsboat_xdg_dirs( newsboat_config_dir, tmp.get_path() + ".local/share/newsboat/", boat_sentries); check(newsboat_config_dir + "urls"); } } SECTION("XDG_DATA_HOME redefined") { const auto data_dir = tmp.get_path() + "xdg-data/"; REQUIRE(::mkdir(data_dir.c_str(), 0700) == 0); xdg_data.set(data_dir); mock_newsbeuter_xdg_dirs( tmp.get_path() + ".config/newsbeuter/", data_dir, beuter_sentries); SECTION("Newsboat uses dotdir") { mock_newsboat_dotdir(tmp, boat_sentries); check(tmp.get_path() + ".newsboat/urls"); } SECTION("Newsboat uses XDG") { const auto newsboat_config_dir = tmp.get_path() + ".config/newsboat/"; mock_newsboat_xdg_dirs( newsboat_config_dir, data_dir + "newsboat/", boat_sentries); check(newsboat_config_dir + "urls"); } } SECTION("Both XDG_CONFIG_HOME and XDG_DATA_HOME redefined") { const auto config_dir = tmp.get_path() + "xdg-conf/"; REQUIRE(::mkdir(config_dir.c_str(), 0700) == 0); xdg_config.set(config_dir); const auto data_dir = tmp.get_path() + "xdg-data/"; REQUIRE(::mkdir(data_dir.c_str(), 0700) == 0); xdg_data.set(data_dir); mock_newsbeuter_xdg_dirs( config_dir + "newsbeuter/", data_dir + "newsbeuter/", beuter_sentries); SECTION("Newsboat uses dotdir") { mock_newsboat_dotdir(tmp, boat_sentries); check(tmp.get_path() + ".newsboat/urls"); } SECTION("Newsboat uses XDG") { const auto newsboat_config_dir = config_dir + "newsboat/"; mock_newsboat_xdg_dirs( newsboat_config_dir, data_dir + "newsboat/", boat_sentries); check(newsboat_config_dir + "urls"); } } } } TEST_CASE("try_migrate_from_newsbeuter() migrates Newsbeuter dotdir from " "default location to default location of Newsboat dotdir", "[ConfigPaths]") { test_helpers::TempDir tmp; test_helpers::EnvVar home("HOME"); home.set(tmp.get_path()); INFO("Temporary directory (used as HOME): " << tmp.get_path()); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); FileSentries sentries; mock_newsbeuter_dotdir(tmp, sentries); ConfigPaths paths; REQUIRE(paths.initialized()); // Files should be migrated, so should return true. REQUIRE(paths.try_migrate_from_newsbeuter()); const auto dotdir = tmp.get_path() + ".newsboat/"; REQUIRE(test_helpers::file_contents(dotdir + "config").at(0) == sentries.config); REQUIRE(test_helpers::file_contents(dotdir + "urls").at(0) == sentries.urls); REQUIRE(test_helpers::file_contents(dotdir + "cache.db").at( 0) == sentries.cache); REQUIRE(test_helpers::file_contents(dotdir + "queue").at(0) == sentries.queue); REQUIRE(test_helpers::file_contents(dotdir + "history.search").at(0) == sentries.search); REQUIRE(test_helpers::file_contents(dotdir + "history.cmdline").at(0) == sentries.cmdline); } TEST_CASE("try_migrate_from_newsbeuter() migrates Newsbeuter XDG dirs from " "their default location to default locations of Newsboat XDG dirs", "[ConfigPaths]") { test_helpers::TempDir tmp; test_helpers::EnvVar home("HOME"); home.set(tmp.get_path()); INFO("Temporary directory (used as HOME): " << tmp.get_path()); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); FileSentries sentries; const auto check = [&] (const std::string& config_dir, const std::string& data_dir) { ConfigPaths paths; REQUIRE(paths.initialized()); // Files should be migrated, so should return true. REQUIRE(paths.try_migrate_from_newsbeuter()); REQUIRE(test_helpers::file_contents(config_dir + "config").at( 0) == sentries.config); REQUIRE(test_helpers::file_contents(config_dir + "urls").at(0) == sentries.urls); REQUIRE(test_helpers::file_contents(data_dir + "cache.db").at( 0) == sentries.cache); REQUIRE(test_helpers::file_contents(data_dir + "queue").at(0) == sentries.queue); REQUIRE(test_helpers::file_contents(data_dir + "history.search").at(0) == sentries.search); REQUIRE(test_helpers::file_contents(data_dir + "history.cmdline").at(0) == sentries.cmdline); }; SECTION("Default XDG locations") { mock_newsbeuter_xdg_dirs(tmp, sentries); const auto config_dir = tmp.get_path() + ".config/newsboat/"; const auto data_dir = tmp.get_path() + ".local/share/newsboat/"; check(config_dir, data_dir); } SECTION("XDG_CONFIG_HOME redefined") { const auto config_dir = tmp.get_path() + "xdg-conf/"; REQUIRE(::mkdir(config_dir.c_str(), 0700) == 0); xdg_config.set(config_dir); mock_newsbeuter_xdg_dirs( config_dir + "newsbeuter/", tmp.get_path() + ".local/share/newsbeuter/", sentries); check(config_dir + "newsboat/", tmp.get_path() + ".local/share/newsboat/"); } SECTION("XDG_DATA_HOME redefined") { const auto data_dir = tmp.get_path() + "xdg-data/"; REQUIRE(::mkdir(data_dir.c_str(), 0700) == 0); xdg_data.set(data_dir); mock_newsbeuter_xdg_dirs( tmp.get_path() + ".config/newsbeuter/", data_dir + "newsbeuter/", sentries); check(tmp.get_path() + ".config/newsboat/", data_dir + "newsboat/"); } SECTION("Both XDG_CONFIG_HOME and XDG_DATA_HOME redefined") { const auto config_dir = tmp.get_path() + "xdg-conf/"; REQUIRE(::mkdir(config_dir.c_str(), 0700) == 0); xdg_config.set(config_dir); const auto data_dir = tmp.get_path() + "xdg-data/"; REQUIRE(::mkdir(data_dir.c_str(), 0700) == 0); xdg_data.set(data_dir); mock_newsbeuter_xdg_dirs( config_dir + "newsbeuter/", data_dir + "newsbeuter/", sentries); check(config_dir + "newsboat/", data_dir + "newsboat/"); } } void verify_xdg_not_migrated( const std::string& config_dir, const std::string& data_dir) { ConfigPaths paths; REQUIRE(paths.initialized()); // Shouldn't migrate anything, so should return false. REQUIRE_FALSE(paths.try_migrate_from_newsbeuter()); REQUIRE_FALSE(0 == ::access((config_dir + "config").c_str(), R_OK)); REQUIRE_FALSE(0 == ::access((config_dir + "urls").c_str(), R_OK)); REQUIRE_FALSE(0 == ::access((data_dir + "cache.db").c_str(), R_OK)); REQUIRE_FALSE(0 == ::access((data_dir + "queue").c_str(), R_OK)); REQUIRE_FALSE(0 == ::access((data_dir + "history.search").c_str(), R_OK)); REQUIRE_FALSE(0 == ::access((data_dir + "history.cmdline").c_str(), R_OK)); } TEST_CASE("try_migrate_from_newsbeuter() doesn't migrate files if empty " "Newsboat XDG config dir already exists", "[ConfigPaths]") { test_helpers::TempDir tmp; test_helpers::EnvVar home("HOME"); home.set(tmp.get_path()); INFO("Temporary directory (used as HOME): " << tmp.get_path()); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); FileSentries sentries; SECTION("Default XDG locations") { mock_newsbeuter_xdg_dirs(tmp, sentries); const auto config_dir = tmp.get_path() + ".config/newsboat/"; REQUIRE(::mkdir(config_dir.c_str(), 0700) == 0); const auto data_dir = tmp.get_path() + ".local/share/newsboat/"; verify_xdg_not_migrated(config_dir, data_dir); } SECTION("XDG_CONFIG_HOME redefined") { const auto config_home = tmp.get_path() + "xdg-conf/"; REQUIRE(::mkdir(config_home.c_str(), 0700) == 0); xdg_config.set(config_home); mock_newsbeuter_xdg_dirs( config_home + "newsbeuter/", tmp.get_path() + ".local/share/newsbeuter/", sentries); const auto config_dir = config_home + "newsboat/"; REQUIRE(::mkdir(config_dir.c_str(), 0700) == 0); verify_xdg_not_migrated(config_dir, tmp.get_path() + ".local/share/newsboat/"); } SECTION("XDG_DATA_HOME redefined") { const auto data_dir = tmp.get_path() + "xdg-data/"; REQUIRE(::mkdir(data_dir.c_str(), 0700) == 0); xdg_data.set(data_dir); mock_newsbeuter_xdg_dirs( tmp.get_path() + ".config/newsbeuter/", data_dir + "newsbeuter/", sentries); const auto config_dir = tmp.get_path() + ".config/newsboat/"; REQUIRE(::mkdir(config_dir.c_str(), 0700) == 0); verify_xdg_not_migrated(config_dir, data_dir + "newsboat/"); } SECTION("Both XDG_CONFIG_HOME and XDG_DATA_HOME redefined") { const auto config_home = tmp.get_path() + "xdg-conf/"; REQUIRE(::mkdir(config_home.c_str(), 0700) == 0); xdg_config.set(config_home); const auto data_dir = tmp.get_path() + "xdg-data/"; REQUIRE(::mkdir(data_dir.c_str(), 0700) == 0); xdg_data.set(data_dir); mock_newsbeuter_xdg_dirs( config_home + "newsbeuter/", data_dir + "newsbeuter/", sentries); const auto config_dir = config_home + "newsboat/"; REQUIRE(::mkdir(config_dir.c_str(), 0700) == 0); verify_xdg_not_migrated(config_dir, data_dir + "newsboat/"); } } TEST_CASE("try_migrate_from_newsbeuter() doesn't migrate files if empty " "Newsboat XDG data dir already exists", "[ConfigPaths]") { test_helpers::TempDir tmp; test_helpers::EnvVar home("HOME"); home.set(tmp.get_path()); INFO("Temporary directory (used as HOME): " << tmp.get_path()); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); FileSentries sentries; SECTION("Default XDG locations") { mock_newsbeuter_xdg_dirs(tmp, sentries); const auto config_dir = tmp.get_path() + ".config/newsboat/"; const auto data_dir = tmp.get_path() + ".local/share/newsboat/"; REQUIRE(::mkdir(data_dir.c_str(), 0700) == 0); verify_xdg_not_migrated(config_dir, data_dir); } SECTION("XDG_CONFIG_HOME redefined") { const auto config_home = tmp.get_path() + "xdg-conf/"; REQUIRE(::mkdir(config_home.c_str(), 0700) == 0); xdg_config.set(config_home); mock_newsbeuter_xdg_dirs( config_home + "newsbeuter/", tmp.get_path() + ".local/share/newsbeuter/", sentries); const auto data_dir = tmp.get_path() + ".local/share/newsboat/"; REQUIRE(::mkdir(data_dir.c_str(), 0700) == 0); verify_xdg_not_migrated(config_home + "newsboat/", data_dir); } SECTION("XDG_DATA_HOME redefined") { const auto data_home = tmp.get_path() + "xdg-data/"; REQUIRE(::mkdir(data_home.c_str(), 0700) == 0); xdg_data.set(data_home); mock_newsbeuter_xdg_dirs( tmp.get_path() + ".config/newsbeuter/", data_home + "newsbeuter/", sentries); const auto data_dir = data_home + "newsboat/"; REQUIRE(::mkdir(data_dir.c_str(), 0700) == 0); verify_xdg_not_migrated(tmp.get_path() + ".config/newsboat/", data_dir); } SECTION("Both XDG_CONFIG_HOME and XDG_DATA_HOME redefined") { const auto config_home = tmp.get_path() + "xdg-conf/"; REQUIRE(::mkdir(config_home.c_str(), 0700) == 0); xdg_config.set(config_home); const auto data_home = tmp.get_path() + "xdg-data/"; REQUIRE(::mkdir(data_home.c_str(), 0700) == 0); xdg_data.set(data_home); mock_newsbeuter_xdg_dirs( config_home + "newsbeuter/", data_home + "newsbeuter/", sentries); const auto data_dir = data_home + "newsboat/"; REQUIRE(::mkdir(data_dir.c_str(), 0700) == 0); verify_xdg_not_migrated(config_home + "newsboat/", data_dir); } } TEST_CASE("try_migrate_from_newsbeuter() doesn't migrate files if Newsboat XDG " "config dir couldn't be created", "[ConfigPaths]") { test_helpers::TempDir tmp; test_helpers::EnvVar home("HOME"); home.set(tmp.get_path()); INFO("Temporary directory (used as HOME): " << tmp.get_path()); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); FileSentries sentries; SECTION("Default XDG locations") { mock_newsbeuter_xdg_dirs(tmp, sentries); // Making XDG .config unwriteable makes it impossible to create // a directory there const auto config_home = tmp.get_path() + ".config/"; test_helpers::Chmod config_home_chmod(config_home, S_IRUSR | S_IXUSR); const auto config_dir = config_home + "newsboat/"; const auto data_dir = tmp.get_path() + ".local/share/newsboat/"; verify_xdg_not_migrated(config_dir, data_dir); } SECTION("XDG_CONFIG_HOME redefined") { const auto config_home = tmp.get_path() + "xdg-conf/"; REQUIRE(::mkdir(config_home.c_str(), 0700) == 0); xdg_config.set(config_home); mock_newsbeuter_xdg_dirs( config_home + "newsbeuter/", tmp.get_path() + ".local/share/newsbeuter/", sentries); // Making XDG .config unwriteable makes it impossible to create // a directory there test_helpers::Chmod config_home_chmod(config_home, S_IRUSR | S_IXUSR); verify_xdg_not_migrated( config_home + "newsboat/", tmp.get_path() + ".local/share/newsboat/"); } SECTION("XDG_DATA_HOME redefined") { const auto data_dir = tmp.get_path() + "xdg-data/"; REQUIRE(::mkdir(data_dir.c_str(), 0700) == 0); xdg_data.set(data_dir); mock_newsbeuter_xdg_dirs( tmp.get_path() + ".config/newsbeuter/", data_dir + "newsbeuter/", sentries); // Making XDG .config unwriteable makes it impossible to create // a directory there const auto config_home = tmp.get_path() + ".config/"; test_helpers::Chmod config_home_chmod(config_home, S_IRUSR | S_IXUSR); verify_xdg_not_migrated( config_home + "newsboat/", data_dir + "newsboat/"); } SECTION("Both XDG_CONFIG_HOME and XDG_DATA_HOME redefined") { const auto config_home = tmp.get_path() + "xdg-conf/"; REQUIRE(::mkdir(config_home.c_str(), 0700) == 0); xdg_config.set(config_home); const auto data_dir = tmp.get_path() + "xdg-data/"; REQUIRE(::mkdir(data_dir.c_str(), 0700) == 0); xdg_data.set(data_dir); mock_newsbeuter_xdg_dirs( config_home + "newsbeuter/", data_dir + "newsbeuter/", sentries); // Making XDG .config unwriteable makes it impossible to create // a directory there test_helpers::Chmod config_home_chmod(config_home, S_IRUSR | S_IXUSR); verify_xdg_not_migrated( config_home + "newsboat/", data_dir + "newsboat/"); } } TEST_CASE("try_migrate_from_newsbeuter() doesn't migrate files if Newsboat XDG " "data dir couldn't be created", "[ConfigPaths]") { test_helpers::TempDir tmp; test_helpers::EnvVar home("HOME"); home.set(tmp.get_path()); INFO("Temporary directory (used as HOME): " << tmp.get_path()); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); FileSentries sentries; SECTION("Default XDG locations") { mock_newsbeuter_xdg_dirs(tmp, sentries); // Making XDG .local/share unwriteable makes it impossible to create // a directory there const auto data_home = tmp.get_path() + ".local/share/"; test_helpers::Chmod data_home_chmod(data_home, S_IRUSR | S_IXUSR); const auto config_dir = tmp.get_path() + ".config/newsboat/"; const auto data_dir = data_home + "newsboat/"; verify_xdg_not_migrated(config_dir, data_dir); } SECTION("XDG_CONFIG_HOME redefined") { const auto config_home = tmp.get_path() + "xdg-conf/"; REQUIRE(::mkdir(config_home.c_str(), 0700) == 0); xdg_config.set(config_home); mock_newsbeuter_xdg_dirs( config_home + "newsbeuter/", tmp.get_path() + ".local/share/newsbeuter/", sentries); // Making XDG .local/share unwriteable makes it impossible to create // a directory there const auto data_home = tmp.get_path() + ".local/share/"; test_helpers::Chmod data_home_chmod(data_home, S_IRUSR | S_IXUSR); verify_xdg_not_migrated( config_home + "newsboat/", data_home + "newsboat/"); } SECTION("XDG_DATA_HOME redefined") { const auto data_home = tmp.get_path() + "xdg-data/"; REQUIRE(::mkdir(data_home.c_str(), 0700) == 0); xdg_data.set(data_home); mock_newsbeuter_xdg_dirs( tmp.get_path() + ".config/newsbeuter/", data_home + "newsbeuter/", sentries); // Making XDG .local/share unwriteable makes it impossible to create // a directory there test_helpers::Chmod data_home_chmod(data_home, S_IRUSR | S_IXUSR); verify_xdg_not_migrated( tmp.get_path() + ".config/newsboat/", data_home + "newsboat/"); } SECTION("Both XDG_CONFIG_HOME and XDG_DATA_HOME redefined") { const auto config_home = tmp.get_path() + "xdg-conf/"; REQUIRE(::mkdir(config_home.c_str(), 0700) == 0); xdg_config.set(config_home); const auto data_home = tmp.get_path() + "xdg-data/"; REQUIRE(::mkdir(data_home.c_str(), 0700) == 0); xdg_data.set(data_home); mock_newsbeuter_xdg_dirs( config_home + "newsbeuter/", data_home + "newsbeuter/", sentries); // Making XDG .local/share unwriteable makes it impossible to create // a directory there test_helpers::Chmod data_home_chmod(data_home, S_IRUSR | S_IXUSR); verify_xdg_not_migrated( config_home + "newsboat/", data_home + "newsboat/"); } } void verify_dotdir_not_migrated(const std::string& dotdir) { ConfigPaths paths; REQUIRE(paths.initialized()); // Shouldn't migrate anything, so should return false. REQUIRE_FALSE(paths.try_migrate_from_newsbeuter()); REQUIRE_FALSE(0 == ::access((dotdir + "config").c_str(), R_OK)); REQUIRE_FALSE(0 == ::access((dotdir + "urls").c_str(), R_OK)); REQUIRE_FALSE(0 == ::access((dotdir + "cache.db").c_str(), R_OK)); REQUIRE_FALSE(0 == ::access((dotdir + "queue").c_str(), R_OK)); REQUIRE_FALSE(0 == ::access((dotdir + "history.search").c_str(), R_OK)); REQUIRE_FALSE(0 == ::access((dotdir + "history.cmdline").c_str(), R_OK)); } TEST_CASE("try_migrate_from_newsbeuter() doesn't migrate files if empty " "Newsboat dotdir already exists", "[ConfigPaths]") { test_helpers::TempDir tmp; test_helpers::EnvVar home("HOME"); home.set(tmp.get_path()); INFO("Temporary directory (used as HOME): " << tmp.get_path()); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); FileSentries sentries; mock_newsbeuter_dotdir(tmp, sentries); const auto dotdir = tmp.get_path() + ".newsboat/"; REQUIRE(::mkdir(dotdir.c_str(), 0700) == 0); verify_dotdir_not_migrated(dotdir); } TEST_CASE("try_migrate_from_newsbeuter() doesn't migrate files if Newsboat " "dotdir couldn't be created", "[ConfigPaths]") { test_helpers::TempDir tmp; test_helpers::EnvVar home("HOME"); home.set(tmp.get_path()); INFO("Temporary directory (used as HOME): " << tmp.get_path()); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); FileSentries sentries; mock_newsbeuter_dotdir(tmp, sentries); // Making home directory unwriteable makes it impossible to create // a directory there test_helpers::Chmod dotdir_chmod(tmp.get_path(), S_IRUSR | S_IXUSR); verify_dotdir_not_migrated(tmp.get_path() + ".newsboat/"); } void verify_create_dirs_returns_false(const test_helpers::TempDir& tmp) { const mode_t readonly = S_IRUSR | S_IXUSR; test_helpers::Chmod home_chmod(tmp.get_path(), readonly); ConfigPaths paths; REQUIRE(paths.initialized()); REQUIRE_FALSE(paths.create_dirs()); } TEST_CASE("create_dirs() returns false if dotdir doesn't exist and couldn't " "be created", "[ConfigPaths]") { test_helpers::TempDir tmp; test_helpers::EnvVar home("HOME"); home.set(tmp.get_path()); INFO("Temporary directory (used as HOME): " << tmp.get_path()); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); verify_create_dirs_returns_false(tmp); } TEST_CASE("create_dirs() returns false if XDG config dir exists but data dir " "doesn't exist and couldn't be created", "[ConfigPaths]") { test_helpers::TempDir tmp; test_helpers::EnvVar home("HOME"); home.set(tmp.get_path()); INFO("Temporary directory (used as HOME): " << tmp.get_path()); // ConfigPaths rely on these variables, so let's sanitize them to ensure // that the tests aren't affected test_helpers::EnvVar xdg_config("XDG_CONFIG_HOME"); xdg_config.unset(); test_helpers::EnvVar xdg_data("XDG_DATA_HOME"); xdg_data.unset(); SECTION("Default XDG locations") { const auto config_dir = tmp.get_path() + ".config/newsboat/"; REQUIRE(utils::mkdir_parents(config_dir, 0700) == 0); verify_create_dirs_returns_false(tmp); } SECTION("XDG_CONFIG_HOME redefined") { const auto config_home = tmp.get_path() + "xdg-cfg/"; xdg_config.set(config_home); const auto config_dir = config_home + "newsboat/"; REQUIRE(utils::mkdir_parents(config_dir, 0700) == 0); verify_create_dirs_returns_false(tmp); } SECTION("XDG_DATA_HOME redefined") { const auto config_dir = tmp.get_path() + ".config/newsboat/"; REQUIRE(utils::mkdir_parents(config_dir, 0700) == 0); const auto data_home = tmp.get_path() + "xdg-data/"; xdg_data.set(data_home); // It's important to set the variable, but *not* create the directory // - it's the pre-condition of the test that the data dir doesn't exist verify_create_dirs_returns_false(tmp); } SECTION("Both XDG_CONFIG_HOME and XDG_DATA_HOME redefined") { const auto config_home = tmp.get_path() + "xdg-cfg/"; xdg_config.set(config_home); const auto config_dir = config_home + "newsboat/"; REQUIRE(utils::mkdir_parents(config_dir, 0700) == 0); const auto data_home = tmp.get_path() + "xdg-data/"; xdg_data.set(data_home); // It's important to set the variable, but *not* create the directory // - it's the pre-condition of the test that the data dir doesn't exist verify_create_dirs_returns_false(tmp); } }