#include "3rd-party/catch.hpp" #include "filepath.h" using namespace newsboat; TEST_CASE("Can be constructed from a string and converted back into a string", "[Filepath]") { const auto example = Filepath::from_locale_string("/etc/hosts"); REQUIRE(example.to_locale_string() == "/etc/hosts"); } TEST_CASE("Can be constructed from non-Unicode data", "[Filepath]") { // See Table 3-7 "Well-Formed UTF-8 Byte Sequences" // https://www.unicode.org/versions/Unicode15.0.0/ch03.pdf#G27506 const std::string input("/invalid: \x81\x82 but it's fine"); const auto filepath = Filepath::from_locale_string(input); REQUIRE(filepath.to_locale_string() == input); } TEST_CASE("Can be displayed even if it contains non-Unicode characters", "[Filepath]") { // See Table 3-7 "Well-Formed UTF-8 Byte Sequences" // https://www.unicode.org/versions/Unicode15.0.0/ch03.pdf#G27506 const std::string input("/invalid: \x80 but it's fine"); const auto filepath = Filepath::from_locale_string(input); // 0xef 0xbf 0xbd is UTF-8 encoding of U+FFFD REPLACEMENT CHARACTER REQUIRE(filepath.display() == "/invalid: \xEF\xBF\xBD but it's fine"); } TEST_CASE("push() adds a new component to the path", "[Filepath]") { auto dir = Filepath::from_locale_string("/tmp"); dir.push(Filepath::from_locale_string("newsboat")); REQUIRE(dir == Filepath::from_locale_string("/tmp/newsboat")); dir.push(Filepath::from_locale_string(".local/share/cache/cache.db")); REQUIRE(dir == Filepath::from_locale_string("/tmp/newsboat/.local/share/cache/cache.db")); } TEST_CASE("push() still adds a separator to non-empty path if new component is empty", "[Filepath]") { auto dir = Filepath::from_locale_string("/root"); dir.push(Filepath::from_locale_string("")); REQUIRE(dir.display() == "/root/"); } TEST_CASE("Can be extended with join()", "[Filepath]") { const auto tmp = Filepath::from_locale_string("/tmp"); const auto subdir = tmp .join(Filepath::from_locale_string("newsboat")) .join(Filepath::from_locale_string("tests")); REQUIRE(subdir == Filepath::from_locale_string("/tmp/newsboat/tests")); } TEST_CASE("join() still adds a separator to non-empty path if new component is empty", "[Filepath]") { const auto path = Filepath::from_locale_string("relative path"); const auto path_with_trailing_slash = path.join(Filepath{}); REQUIRE(path_with_trailing_slash.display() == "relative path/"); } TEST_CASE("Can be copied", "[Filepath]") { auto original = Filepath::from_locale_string("/etc/hosts"); const auto check = [&original](const Filepath& copy) { REQUIRE(original == copy); REQUIRE(original.display() == "/etc/hosts"); // Demonstrate that changing the original object doesn't modify the copy original.push(Filepath::from_locale_string(" a bit more")); REQUIRE(original.display() == "/etc/hosts/ a bit more"); REQUIRE(copy.display() == "/etc/hosts"); }; SECTION("with copy constructor") { const Filepath copy(original); check(copy); } SECTION("with copy assignment operator") { const Filepath copy = original; check(copy); } } TEST_CASE("Can't set extension for an empty path", "[Filepath]") { Filepath path; REQUIRE_FALSE(path.set_extension("exe")); } TEST_CASE("Can set extension for non-empty path", "[Filepath]") { auto path = Filepath::from_locale_string("file"); SECTION("extension is UTF-8") { REQUIRE(path.set_extension("exe")); REQUIRE(path == Filepath::from_locale_string("file.exe")); } SECTION("extension is not a valid UTF-8 string") { REQUIRE(path.set_extension("\x80")); REQUIRE(path == Filepath::from_locale_string("file.\x80")); } } TEST_CASE("Can check if path is absolute", "[Filepath]") { Filepath path; SECTION("empty path is not absolute") { REQUIRE_FALSE(path.is_absolute()); } SECTION("path that starts with a slash is absolute") { path.push(Filepath::from_locale_string("/etc")); REQUIRE(path.display() == "/etc"); REQUIRE(path.is_absolute()); path.push(Filepath::from_locale_string("ca-certificates")); REQUIRE(path.display() == "/etc/ca-certificates"); REQUIRE(path.is_absolute()); } SECTION("path that doesn't start with a slash is not absolute") { path.push(Filepath::from_locale_string("vmlinuz")); REQUIRE(path.display() == "vmlinuz"); REQUIRE_FALSE(path.is_absolute()); path.push(Filepath::from_locale_string("undefined")); REQUIRE(path.display() == "vmlinuz/undefined"); REQUIRE_FALSE(path.is_absolute()); } } TEST_CASE("Can check if path starts with a given base path", "[Filepath]") { SECTION("Empty path") { const Filepath path; SECTION("Base path is empty") { REQUIRE(path.starts_with(Filepath{})); } SECTION("Base path is not empty") { REQUIRE_FALSE(path.starts_with(Filepath::from_locale_string("/etc"))); } } SECTION("Non-empty path that doesn't start with the base") { const auto path = Filepath::from_locale_string("/etcetera"); const auto base = Filepath::from_locale_string("/etc"); REQUIRE_FALSE(path.starts_with(base)); } SECTION("Non-empty path that starts with the base") { const auto path = Filepath::from_locale_string("/usr/local/bin/newsboat"); const auto base = Filepath::from_locale_string("/usr/local"); REQUIRE(path.starts_with(base)); } SECTION("Base is not a valid UTF-8 string") { const auto path = Filepath::from_locale_string("/test\x81\x82/foobar"); SECTION("Path doesn't start with base") { const auto base = Filepath::from_locale_string("baz\x80quux"); REQUIRE_FALSE(path.starts_with(base)); } SECTION("Path starts with base") { const auto base = Filepath::from_locale_string("/test\x81\x82/"); REQUIRE(path.starts_with(base)); } } } TEST_CASE("Can extract the final component of the path (file or directory name)", "[Filepath]") { SECTION("Empty path has no final component") { const Filepath path; REQUIRE(path.file_name() == nonstd::nullopt); } SECTION("The final component of a path with single level is the path itself") { const auto path = Filepath::from_locale_string("hello"); REQUIRE(path.file_name().value() == path); } SECTION("Multi-level path") { const auto path = Filepath::from_locale_string("/dev/pts/0"); REQUIRE(path.file_name().value() == Filepath::from_locale_string("0")); } SECTION("Final component is not a valid UTF-8 string") { const auto path = Filepath::from_locale_string("/whatever/one\x80two"); REQUIRE(path.file_name().value() == Filepath::from_locale_string("one\x80two")); } } TEST_CASE("Can be ordered lexicographically", "[Filepath]") { const auto root = Filepath::from_locale_string("/"); const auto var_log = Filepath::from_locale_string("/var/log"); const auto home_minoru = Filepath::from_locale_string("/home/minoru"); const auto home_minoru_src_newsboat = Filepath::from_locale_string("/home/minoru/src/newsboat"); SECTION("operator<") { SECTION("Path to directory is less than the path to its subdirectory") { REQUIRE(root < var_log); REQUIRE(root < home_minoru); REQUIRE(home_minoru < home_minoru_src_newsboat); REQUIRE_FALSE(home_minoru_src_newsboat < root); } SECTION("Disparate paths are ordered lexicographically") { REQUIRE(home_minoru < var_log); REQUIRE(home_minoru_src_newsboat < var_log); REQUIRE_FALSE(home_minoru_src_newsboat < home_minoru); } } SECTION("operator>") { SECTION("Path to subdirectory is greater than the path to its parent directory") { REQUIRE(var_log > root); REQUIRE(home_minoru > root); REQUIRE(home_minoru_src_newsboat > home_minoru); REQUIRE_FALSE(root > home_minoru_src_newsboat); } SECTION("Disparate paths are ordered lexicographically") { REQUIRE(var_log > home_minoru); REQUIRE(var_log > home_minoru_src_newsboat); REQUIRE_FALSE(home_minoru > home_minoru_src_newsboat); } } SECTION("operator<=") { SECTION("Any path is less than or equal to itself") { REQUIRE(root <= root); REQUIRE(var_log <= var_log); REQUIRE(home_minoru <= home_minoru); REQUIRE(home_minoru_src_newsboat <= home_minoru_src_newsboat); } SECTION("Path to directory is less than or equal to the path to its subdirectory") { REQUIRE(root <= var_log); REQUIRE(root <= home_minoru); REQUIRE(home_minoru <= home_minoru_src_newsboat); REQUIRE_FALSE(home_minoru_src_newsboat <= root); } SECTION("Disparate paths are ordered lexicographically") { REQUIRE(home_minoru <= var_log); REQUIRE(home_minoru_src_newsboat <= var_log); REQUIRE_FALSE(home_minoru_src_newsboat <= home_minoru); } } SECTION("operator>=") { SECTION("Any path is greater than or equal to itself") { REQUIRE(root >= root); REQUIRE(var_log >= var_log); REQUIRE(home_minoru >= home_minoru); REQUIRE(home_minoru_src_newsboat >= home_minoru_src_newsboat); } SECTION("Path to subdirectory is greater than or equal to the path to its parent directory") { REQUIRE(var_log >= root); REQUIRE(home_minoru >= root); REQUIRE(home_minoru_src_newsboat >= home_minoru); REQUIRE_FALSE(root >= home_minoru_src_newsboat); } SECTION("Disparate paths are ordered lexicographically") { REQUIRE(var_log >= home_minoru); REQUIRE(var_log >= home_minoru_src_newsboat); REQUIRE_FALSE(home_minoru >= home_minoru_src_newsboat); } } }