diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/cpp/.clang-tidy | 13 | ||||
-rw-r--r-- | test/cpp/test_path_std.cpp | 496 | ||||
-rw-r--r-- | test/headers/test_headers.c | 1 | ||||
-rw-r--r-- | test/test_path.c | 948 |
4 files changed, 1458 insertions, 0 deletions
diff --git a/test/cpp/.clang-tidy b/test/cpp/.clang-tidy new file mode 100644 index 0000000..9a3cd8c --- /dev/null +++ b/test/cpp/.clang-tidy @@ -0,0 +1,13 @@ +# Copyright 2020-2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: 0BSD OR ISC + +Checks: > + -*-avoid-c-arrays, + -*-no-malloc, + -android-cloexec-fopen, + -cppcoreguidelines-owning-memory, + -fuchsia-default-arguments-calls, + -modernize-raw-string-literal, + -modernize-use-trailing-return-type, + -readability-implicit-bool-conversion, +InheritParentConfig: true diff --git a/test/cpp/test_path_std.cpp b/test/cpp/test_path_std.cpp new file mode 100644 index 0000000..f6767a6 --- /dev/null +++ b/test/cpp/test_path_std.cpp @@ -0,0 +1,496 @@ +// Copyright 2020-2022 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +/* + Tests that compare results with std::filesystem::path. Also serves as a + handy record of divergence between zix, the C++ standard library, and + different implementations of it. The inconsistencies are confined to + Windows. +*/ + +#undef NDEBUG + +#include "zix/path.h" +#include "zix/string_view.h" + +#include <cassert> +#include <cstdlib> +#include <filesystem> +#include <sstream> +#include <string> + +struct BinaryCase { + const char* lhs; + const char* rhs; +}; + +static const BinaryCase joins[] = { + {"", ""}, {"", "/b/"}, {"", "b"}, {"/", ""}, + {"..", ".."}, {"..", "name"}, {"..", "/"}, {"/", ".."}, + {"/", nullptr}, {"//host", ""}, {"//host", "a"}, {"//host/", "a"}, + {"/a", ""}, {"/a", "/b"}, {"/a", "/b/"}, {"/a", "b"}, + {"/a", "b/"}, {"/a", nullptr}, {"/a/", ""}, {"/a/", "b"}, + {"/a/", "b/"}, {"/a/", nullptr}, {"/a/b", nullptr}, {"/a/b/", ""}, + {"/a/b/", nullptr}, {"/a/c", "b"}, {"/a/c/", "/b/d"}, {"/a/c/", "b"}, + {"C:", ""}, {"C:/a", "/b"}, {"C:/a", "C:/b"}, {"C:/a", "C:b"}, + {"C:/a", "D:b"}, {"C:/a", "b"}, {"C:/a", "b/"}, {"C:/a", "c:/b"}, + {"C:/a", "c:b"}, {"C:\\a", "b"}, {"C:\\a", "b\\"}, {"C:a", "/b"}, + {"C:a", "C:/b"}, {"C:a", "C:\\b"}, {"C:a", "C:b"}, {"C:a", "b"}, + {"C:a", "c:/b"}, {"C:a", "c:\\b"}, {"C:a", "c:b"}, {"a", ""}, + {"a", "/b"}, {"a", "C:"}, {"a", "C:/b"}, {"a", "C:\\b"}, + {"a", "\\b"}, {"a", "b"}, {"a", "b/"}, {"a", nullptr}, + {"a/", ""}, {"a/", "/b"}, {"a/", "b"}, {"a/", "b/"}, + {"a/", nullptr}, {"a/b", ""}, {"a/b", nullptr}, {"a/b/", ""}, + {"a/b/", nullptr}, {"a\\", "\\b"}, {"a\\", "b"}, {nullptr, "/b"}, + {nullptr, "/b/c"}, {nullptr, "b"}, {nullptr, "b/c"}, {nullptr, nullptr}, +}; + +static const BinaryCase lexical_relatives[] = { + {nullptr, nullptr}, + + {"", ""}, + {"", "."}, + {".", "."}, + {"../", "../"}, + {"../", "./"}, + {"../", "a"}, + {"../../a", "../b"}, + {"../a", "../b"}, + {"/", "/a/b"}, + {"/", "/a/b/c"}, + {"/", "a"}, + {"/", "a/b"}, + {"/", "a/b/c"}, + {"//host", "//host"}, + {"//host/", "//host/"}, + {"//host/a/b", "//host/a/b"}, + {"/a", "/b/c/"}, + {"/a/", "/a/b"}, + {"/a/", "/b/c"}, + {"/a/", "/b/c/"}, + {"/a/", "b"}, + {"/a/", "b/c/"}, + {"/a/b", "a/b"}, + {"/a/b/c", "/a/b/d/"}, + {"/a/b/c", "/a/b/d/e/"}, + {"/a/b/c", "/a/d"}, + {"C:", "C:a.txt"}, + {"C:", "D:a.txt"}, + {"C:../", "C:../"}, + {"C:../", "C:./"}, + {"C:../", "C:a"}, + {"C:../../a", "C:../b"}, + {"C:../a", "C:../b"}, + {"C:/", "C:a.txt"}, + {"C:/", "D:a.txt"}, + {"C:/D/", "C:F"}, + {"C:/D/", "C:F.txt"}, + {"C:/D/S/", "F"}, + {"C:/D/S/", "F.txt"}, + {"C:/Dir/", "C:/Dir/File.txt"}, + {"C:/Dir/", "C:File.txt"}, + {"C:/Dir/File.txt", "C:/Dir/Sub/"}, + {"C:/Dir/File.txt", "C:/Other.txt"}, + {"C:/Dir/Sub/", "File.txt"}, + {"C:/a.txt", "C:/b/c.txt"}, + {"C:/a/", "C:/a/b.txt"}, + {"C:/a/", "C:b.txt"}, + {"C:/a/b.txt", "C:/a/b/"}, + {"C:/a/b.txt", "C:/d.txt"}, + {"C:/a/b/", "d.txt"}, + {"C:/b/", "C:a.txt"}, + {"C:/b/c/", "a.txt"}, + {"C:F", "D:G"}, + {"C:File.txt", "D:Other.txt"}, + {"C:a.txt", "C:b.txt"}, + {"C:a.txt", "D:"}, + {"C:a.txt", "D:/"}, + {"C:a.txt", "D:e.txt"}, + {"C:a/b/c", "C:../"}, + {"C:a/b/c", "C:../../"}, + {"a", "a"}, + {"a", "a/b/c"}, + {"a", "b/c"}, + {"a/b", "a/b"}, + {"a/b", "c/d"}, + {"a/b/c", "../"}, + {"a/b/c", "../../"}, + {"a/b/c", "a/b/c"}, + {"a/b/c", "a/b/c/x/y"}, + {"a/b/c/", "a/b/c/"}, + +// Network paths that aren't supported by MinGW +#if !(defined(_WIN32) && defined(__GLIBCXX__)) + {"//host", "//host/"}, + {"//host", "a"}, + {"//host", "a"}, + {"//host/", "a"}, + {"//host/", "a"}, +#endif +}; + +static const char* const paths[] = { + // Valid paths handled consistently on all platforms + "", + ".", + "..", + "../", + "../..", + "../../", + "../../a", + "../a", + "../name", + "..\\..\\a", + "..\\a", + "..name", + "./", + "./.", + "./..", + ".//a//.//./b/.//", + "./a/././b/./", + "./name", + ".hidden", + ".hidden.txt", + "/", + "/.", + "/..", + "/../", + "/../..", + "/../../", + "/../a", + "/../a/../..", + "//", + "///", + "///dir/", + "///dir///", + "///dir///name", + "///dir///sub/////", + "///name", + "/a", + "/a/", + "/a/b", + "/a/b/", + "/a\\", + "/a\\b", + "/a\\b/", + "/dir/", + "/dir/.", + "/dir/..", + "/dir/../..", + "/dir/.hidden", + "/dir//name", + "/dir//sub/suub/", + "/dir/name", + "/dir/name.tar.gz", + "/dir/name.txt", + "/dir/name\\", + "/dir/sub/", + "/dir/sub/./", + "/dir/sub//", + "/dir/sub///", + "/dir/sub//name", + "/dir/sub/name", + "/dir/sub/suub/", + "/dir/sub/suub/../", + "/dir/sub/suub/../d", + "/dir/sub/suub/../d/", + "/dir/sub/suub/suuub/", + "/dir/sub\\name", + "/name", + "/name.", + "/name.txt", + "/name.txt.", + "C:", + "C:.", + "C:..", + "C:/", + "C:/.", + "C:/..", + "C:/../../name", + "C:/../name", + "C:/..dir/..name", + "C:/..name", + "C:/./name", + "C:/.dir/../name", + "C:/.dir/.hidden", + "C:/.hidden", + "C:/dir/", + "C:/dir/.", + "C:/dir/..", + "C:/dir/name", + "C:/dir/sub/", + "C:/dir/sub/name", + "C:/dir/sub/suub/", + "C:/dir/sub/suub/suuub/", + "C:/name", + "C:/name.", + "C:/name.txt", + "C:/name\\horror", + "C:/name\\horror/", + "C:\\", + "C:\\a", + "C:\\a/", + "C:\\a/b", + "C:\\a/b\\", + "C:\\a\\", + "C:\\a\\b", + "C:\\a\\b\\", + "C:\\a\\b\\c", + "C:\\a\\b\\d\\", + "C:\\a\\b\\d\\e\\", + "C:\\b", + "C:\\b\\c\\", + "C:a", + "C:dir/", + "C:dir/name", + "C:dir\\", + "C:name", + "C:name/", + "C:name\\horror", + "D|\\dir\\name", + "D|\\name", + "D|name", + "Z:/a/b", + "Z:\\a\\b", + "Z:b", + "\\", + "\\a\\/b\\/c\\", + "\\a\\b\\c\\", + "\\b", + "a", + "a.", + "a..txt", + "a.txt", + "a/b", + "a/b\\", + "c:/name", + "c:\\name", + "c:name", + "dir/", + "dir/.", + "dir/..", + "dir/../", + "dir/../b", + "dir/../b/../..///", + "dir/../b/..//..///../", + "dir/../sub/../name", + "dir/./", + "dir/.///b/../", + "dir/./b", + "dir/./b/.", + "dir/./b/..", + "dir/./sub/./name", + "dir///b", + "dir//b", + "dir/\\b", + "dir/b", + "dir/b.", + "dir/name", + "dir/name.", + "dir/name.tar.gz", + "dir/name.txt", + "dir/name.txt.", + "dir/name\\with\\backslashes", + "dir/sub/", + "dir/sub/.", + "dir/sub/..", + "dir/sub/../", + "dir/sub/../..", + "dir/sub/../../", + "dir/sub/../name", + "dir/sub/./", + "dir/sub/./..", + "dir/sub/./../", + "dir/sub/./name", + "dir/sub//", + "dir/sub///", + "dir/sub/name", + "dir/sub/suub/../..", + "dir/sub/suub/../../", + "dir/weird<sub/weird%name", + "dir\\", + "dir\\/\\a", + "dir\\/a", + "dir\\a", + "dir\\a/", + "name", + "name-snakey", + "name.", + "name..", + "name.txt", + "name.txt.", + "name/", + "name//", + "name///", + "name_sneaky", + + // Filenames with colons that are handled consistently everywhere + "C:/a/C:/b", + "C:/a/c:/b", + "C:a/C:/b", + "C:a/C:\\b", + "C:a/c:/b", + "C:a/c:\\b", + +#if !defined(_WIN32) + // Filenames with colons that aren't handled consistently on Windows + "C:/a/C:b", + "C:/a/D:b", + "C:/a/c:b", + "C:a/C:b", + "C:a/c:b", + "NO:DRIVE", + "NODRIVE:", + "dir/C:", + "dir/C:/name", + "dir/C:\\name", + "no:drive", + "nodrive:", +#endif + +#if !defined(_WIN32) + // Invalid root and network paths that aren't handled consistently on Windows + "//a//", + "//host/dir/../../../a", + "//host/dir/../../a", + "C://", +#endif + +#if !(defined(_WIN32) && defined(__GLIBCXX__)) + // Network paths that aren't supported by MinGW + "//h", + "//h/", + "//h/a", + "//h/a.txt", + "//h/d/a.txt", + "//h/d/s/a.txt", + "//host", + "//host/", + "//host/.", + "//host/..", + "//host/../dir/", + "//host/../name", + "//host/..dir/", + "//host/..name", + "//host/./dir/", + "//host/./name", + "//host/.dir/", + "//host/.name", + "//host/C:/dir/name.txt", + "//host/C:/name", + "//host/dir/.", + "//host/dir/..", + "//host/dir/../name", + "//host/dir/./name", + "//host/dir/name", + "//host/dir/sub/name", + "//host/name", + "//host/name.", + "//host/name..", + "//host\\", + "//host\\name.txt", + "\\\\host", + "\\\\host\\", + "\\\\host\\C:\\name", + "\\\\host\\dir\\name.txt", + "\\\\host\\name.txt", +#endif +}; + +/// Abort if `path` doesn't match `result` when loosely parsed +static bool +match(const std::filesystem::path& path, char* const result) +{ + const bool success = (path.empty() && !result) || (result && path == result); + + free(result); + return success; +} + +/// Abort if `path` doesn't equal `result` +static bool +equal(const std::filesystem::path& path, char* const result) +{ + const bool success = + (path.empty() && !result) || (result && path.u8string() == result); + + free(result); + return success; +} + +/// Abort if `path` doesn't match `view` when loosely parsed +static bool +match(const std::filesystem::path& path, const ZixStringView view) +{ + return (path.empty() && !view.length) || + (view.length && path == std::string{view.data, view.length}); +} + +/// Abort if `path` doesn't equal `view` +static bool +equal(const std::filesystem::path& path, const ZixStringView view) +{ + return (path.empty() && !view.length) || + (view.length && + path.u8string() == std::string{view.data, view.length}); +} + +int +main() +{ + using Path = std::filesystem::path; + + for (const char* string : paths) { + const Path path{string}; + + // Decomposition + assert(equal(path.root_name(), zix_path_root_name(string))); + assert(match(path.root_directory(), zix_path_root_directory(string))); + assert(match(path.root_path(), zix_path_root_path(string))); + assert(equal(path.relative_path(), zix_path_relative_path(string))); + assert(match(path.parent_path(), zix_path_parent_path(string))); + assert(equal(path.filename(), zix_path_filename(string))); + assert(equal(path.stem(), zix_path_stem(string))); + assert(equal(path.extension(), zix_path_extension(string))); + + // Queries + assert(path.has_root_path() == zix_path_has_root_path(string)); + assert(path.has_root_name() == zix_path_has_root_name(string)); + assert(path.has_root_directory() == zix_path_has_root_directory(string)); + assert(path.has_relative_path() == zix_path_has_relative_path(string)); + assert(path.has_parent_path() == zix_path_has_parent_path(string)); + assert(path.has_filename() == zix_path_has_filename(string)); + assert(path.has_stem() == zix_path_has_stem(string)); + assert(path.has_extension() == zix_path_has_extension(string)); + assert(path.is_absolute() == zix_path_is_absolute(string)); + assert(path.is_relative() == zix_path_is_relative(string)); + + // Generation + + assert( + equal(Path{path}.make_preferred(), zix_path_preferred(nullptr, string))); + + assert(match(path.lexically_normal(), + zix_path_lexically_normal(nullptr, string))); + } + + for (const auto& join : joins) { + const Path l = join.lhs ? Path{join.lhs} : Path{}; + const Path r = join.rhs ? Path{join.rhs} : Path{}; + + assert(equal(l / r, zix_path_join(nullptr, join.lhs, join.rhs))); + } + + for (const auto& relatives : lexical_relatives) { + const Path l = relatives.lhs ? Path{relatives.lhs} : Path{}; + const Path r = relatives.rhs ? Path{relatives.rhs} : Path{}; + + assert(match( + l.lexically_relative(r), + zix_path_lexically_relative(nullptr, relatives.lhs, relatives.rhs))); + + assert(match( + r.lexically_relative(l), + zix_path_lexically_relative(nullptr, relatives.rhs, relatives.lhs))); + } +} diff --git a/test/headers/test_headers.c b/test/headers/test_headers.c index dc580ea..9e5bddd 100644 --- a/test/headers/test_headers.c +++ b/test/headers/test_headers.c @@ -9,6 +9,7 @@ #include "zix/digest.h" // IWYU pragma: keep #include "zix/function_types.h" // IWYU pragma: keep #include "zix/hash.h" // IWYU pragma: keep +#include "zix/path.h" // IWYU pragma: keep #include "zix/ring.h" // IWYU pragma: keep #include "zix/sem.h" // IWYU pragma: keep #include "zix/status.h" // IWYU pragma: keep diff --git a/test/test_path.c b/test/test_path.c new file mode 100644 index 0000000..5c98c32 --- /dev/null +++ b/test/test_path.c @@ -0,0 +1,948 @@ +// Copyright 2020-2022 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +#undef NDEBUG + +#include "zix/path.h" +#include "zix/string_view.h" + +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +/// Abort if `string` doesn't equal `expected` +static bool +equal(char* const string, const char* const expected) +{ + const bool result = + (!string && !expected) || (string && expected && !strcmp(string, expected)); + + free(string); + return result; +} + +/// Abort if `string` doesn't equal `expected` with preferred separators +static bool +match(char* const string, const char* const expected) +{ + char* const e = expected ? zix_path_preferred(NULL, expected) : NULL; + char* const s = string ? zix_path_preferred(NULL, string) : NULL; + + const bool result = (!s && !e) || (s && e && !strcmp(s, e)); + + free(s); + free(e); + free(string); + return result; +} + +/// Abort if `view` doesn't equal `expected` +static bool +view_equal(const ZixStringView view, const char* const expected) +{ + const bool result = + (!view.length && !expected) || + (view.length && expected && strlen(expected) == view.length && + !strncmp(view.data, expected, view.length)); + + return result; +} + +static void +test_path_join(void) +{ + // Trivial cases + assert(equal(zix_path_join(NULL, "", ""), "")); + assert(equal(zix_path_join(NULL, "", "/b/"), "/b/")); + assert(equal(zix_path_join(NULL, "", "b"), "b")); + assert(equal(zix_path_join(NULL, "/", ""), "/")); + assert(equal(zix_path_join(NULL, "/a/", ""), "/a/")); + assert(equal(zix_path_join(NULL, "/a/b/", ""), "/a/b/")); + assert(equal(zix_path_join(NULL, "a/", ""), "a/")); + assert(equal(zix_path_join(NULL, "a/b/", ""), "a/b/")); + + // Null is treated like an empty path + assert(equal(zix_path_join(NULL, "/", NULL), "/")); + assert(equal(zix_path_join(NULL, "/a/", NULL), "/a/")); + assert(equal(zix_path_join(NULL, "/a/b/", NULL), "/a/b/")); + assert(equal(zix_path_join(NULL, "a/", NULL), "a/")); + assert(equal(zix_path_join(NULL, "a/b/", NULL), "a/b/")); + assert(equal(zix_path_join(NULL, NULL, "/b"), "/b")); + assert(equal(zix_path_join(NULL, NULL, "/b/c"), "/b/c")); + assert(equal(zix_path_join(NULL, NULL, "b"), "b")); + assert(equal(zix_path_join(NULL, NULL, "b/c"), "b/c")); + assert(equal(zix_path_join(NULL, NULL, NULL), "")); + + // Joining an absolute path discards the left path + assert(equal(zix_path_join(NULL, "/a", "/b"), "/b")); + assert(equal(zix_path_join(NULL, "/a", "/b/"), "/b/")); + assert(equal(zix_path_join(NULL, "a", "/b"), "/b")); + assert(equal(zix_path_join(NULL, "a/", "/b"), "/b")); + + // Concatenation cases + assert(equal(zix_path_join(NULL, "/a/", "b"), "/a/b")); + assert(equal(zix_path_join(NULL, "/a/", "b/"), "/a/b/")); + assert(equal(zix_path_join(NULL, "a/", "b"), "a/b")); + assert(equal(zix_path_join(NULL, "a/", "b/"), "a/b/")); + assert(equal(zix_path_join(NULL, "/a/c/", "/b/d"), "/b/d")); + assert(equal(zix_path_join(NULL, "/a/c/", "b"), "/a/c/b")); + +#ifdef _WIN32 + // "C:" is a drive letter, "//host" is a share, backslash is a separator + assert(equal(zix_path_join(NULL, "//host", ""), "//host\\")); + assert(equal(zix_path_join(NULL, "//host", "a"), "//host\\a")); + assert(equal(zix_path_join(NULL, "//host/", "a"), "//host/a")); + assert(equal(zix_path_join(NULL, "/a", ""), "/a\\")); + assert(equal(zix_path_join(NULL, "/a", "b"), "/a\\b")); + assert(equal(zix_path_join(NULL, "/a", "b/"), "/a\\b/")); + assert(equal(zix_path_join(NULL, "/a", NULL), "/a\\")); + assert(equal(zix_path_join(NULL, "/a/b", NULL), "/a/b\\")); + assert(equal(zix_path_join(NULL, "/a/c", "b"), "/a/c\\b")); + assert(equal(zix_path_join(NULL, "C:", ""), "C:")); + assert(equal(zix_path_join(NULL, "C:/a", "/b"), "C:/b")); + assert(equal(zix_path_join(NULL, "C:/a", "C:/b"), "C:/b")); + assert(equal(zix_path_join(NULL, "C:/a", "C:b"), "C:/a\\b")); + assert(equal(zix_path_join(NULL, "C:/a", "D:b"), "D:b")); + assert(equal(zix_path_join(NULL, "C:/a", "b"), "C:/a\\b")); + assert(equal(zix_path_join(NULL, "C:/a", "b/"), "C:/a\\b/")); + assert(equal(zix_path_join(NULL, "C:/a", "c:/b"), "c:/b")); + assert(equal(zix_path_join(NULL, "C:/a", "c:b"), "c:b")); + assert(equal(zix_path_join(NULL, "C:\\a", "b"), "C:\\a\\b")); + assert(equal(zix_path_join(NULL, "C:\\a", "b\\"), "C:\\a\\b\\")); + assert(equal(zix_path_join(NULL, "C:a", "/b"), "C:/b")); + assert(equal(zix_path_join(NULL, "C:a", "C:/b"), "C:/b")); + assert(equal(zix_path_join(NULL, "C:a", "C:\\b"), "C:\\b")); + assert(equal(zix_path_join(NULL, "C:a", "C:b"), "C:a\\b")); + assert(equal(zix_path_join(NULL, "C:a", "b"), "C:a\\b")); + assert(equal(zix_path_join(NULL, "C:a", "c:/b"), "c:/b")); + assert(equal(zix_path_join(NULL, "C:a", "c:\\b"), "c:\\b")); + assert(equal(zix_path_join(NULL, "C:a", "c:b"), "c:b")); + assert(equal(zix_path_join(NULL, "a", ""), "a\\")); + assert(equal(zix_path_join(NULL, "a", "C:"), "C:")); + assert(equal(zix_path_join(NULL, "a", "C:/b"), "C:/b")); + assert(equal(zix_path_join(NULL, "a", "C:\\b"), "C:\\b")); + assert(equal(zix_path_join(NULL, "a", "\\b"), "\\b")); + assert(equal(zix_path_join(NULL, "a", "b"), "a\\b")); + assert(equal(zix_path_join(NULL, "a", "b/"), "a\\b/")); + assert(equal(zix_path_join(NULL, "a", NULL), "a\\")); + assert(equal(zix_path_join(NULL, "a/b", ""), "a/b\\")); + assert(equal(zix_path_join(NULL, "a/b", NULL), "a/b\\")); + assert(equal(zix_path_join(NULL, "a\\", "\\b"), "\\b")); + assert(equal(zix_path_join(NULL, "a\\", "b"), "a\\b")); +#else + // "C:" isn't special, "//host" has extra slashes, slash is the only separator + assert(equal(zix_path_join(NULL, "//host", ""), "//host/")); + assert(equal(zix_path_join(NULL, "//host", "a"), "//host/a")); + assert(equal(zix_path_join(NULL, "//host/", "a"), "//host/a")); + assert(equal(zix_path_join(NULL, "/a", ""), "/a/")); + assert(equal(zix_path_join(NULL, "/a", "b"), "/a/b")); + assert(equal(zix_path_join(NULL, "/a", "b/"), "/a/b/")); + assert(equal(zix_path_join(NULL, "/a", NULL), "/a/")); + assert(equal(zix_path_join(NULL, "/a/b", NULL), "/a/b/")); + assert(equal(zix_path_join(NULL, "/a/c", "b"), "/a/c/b")); + assert(equal(zix_path_join(NULL, "C:", ""), "C:/")); + assert(equal(zix_path_join(NULL, "C:/a", "/b"), "/b")); + assert(equal(zix_path_join(NULL, "C:/a", "C:/b"), "C:/a/C:/b")); + assert(equal(zix_path_join(NULL, "C:/a", "C:b"), "C:/a/C:b")); + assert(equal(zix_path_join(NULL, "C:/a", "D:b"), "C:/a/D:b")); + assert(equal(zix_path_join(NULL, "C:/a", "b"), "C:/a/b")); + assert(equal(zix_path_join(NULL, "C:/a", "b/"), "C:/a/b/")); + assert(equal(zix_path_join(NULL, "C:/a", "c:/b"), "C:/a/c:/b")); + assert(equal(zix_path_join(NULL, "C:/a", "c:b"), "C:/a/c:b")); + assert(equal(zix_path_join(NULL, "C:\\a", "b"), "C:\\a/b")); + assert(equal(zix_path_join(NULL, "C:\\a", "b\\"), "C:\\a/b\\")); + assert(equal(zix_path_join(NULL, "C:a", "/b"), "/b")); + assert(equal(zix_path_join(NULL, "C:a", "C:/b"), "C:a/C:/b")); + assert(equal(zix_path_join(NULL, "C:a", "C:\\b"), "C:a/C:\\b")); + assert(equal(zix_path_join(NULL, "C:a", "C:b"), "C:a/C:b")); + assert(equal(zix_path_join(NULL, "C:a", "b"), "C:a/b")); + assert(equal(zix_path_join(NULL, "C:a", "c:/b"), "C:a/c:/b")); + assert(equal(zix_path_join(NULL, "C:a", "c:\\b"), "C:a/c:\\b")); + assert(equal(zix_path_join(NULL, "C:a", "c:b"), "C:a/c:b")); + assert(equal(zix_path_join(NULL, "a", ""), "a/")); + assert(equal(zix_path_join(NULL, "a", "C:"), "a/C:")); + assert(equal(zix_path_join(NULL, "a", "C:/b"), "a/C:/b")); + assert(equal(zix_path_join(NULL, "a", "C:\\b"), "a/C:\\b")); + assert(equal(zix_path_join(NULL, "a", "\\b"), "a/\\b")); + assert(equal(zix_path_join(NULL, "a", "b"), "a/b")); + assert(equal(zix_path_join(NULL, "a", "b/"), "a/b/")); + assert(equal(zix_path_join(NULL, "a", NULL), "a/")); + assert(equal(zix_path_join(NULL, "a/b", ""), "a/b/")); + assert(equal(zix_path_join(NULL, "a/b", NULL), "a/b/")); + assert(equal(zix_path_join(NULL, "a\\", "\\b"), "a\\/\\b")); + assert(equal(zix_path_join(NULL, "a\\", "b"), "a\\/b")); +#endif +} + +static void +test_path_preferred(void) +{ + assert(equal(zix_path_preferred(NULL, ""), "")); + assert(equal(zix_path_preferred(NULL, "some-name"), "some-name")); + assert(equal(zix_path_preferred(NULL, "some_name"), "some_name")); + +#ifdef _WIN32 + // Backslash is the preferred separator + assert(equal(zix_path_preferred(NULL, "/"), "\\")); + assert(equal(zix_path_preferred(NULL, "/."), "\\.")); + assert(equal(zix_path_preferred(NULL, "//a"), "\\\\a")); + assert(equal(zix_path_preferred(NULL, "//a/"), "\\\\a\\")); + assert(equal(zix_path_preferred(NULL, "//a//"), "\\\\a\\\\")); + assert(equal(zix_path_preferred(NULL, "/a//b/c/"), "\\a\\\\b\\c\\")); + assert(equal(zix_path_preferred(NULL, "/a/b/c/"), "\\a\\b\\c\\")); + assert(equal(zix_path_preferred(NULL, "\\"), "\\")); + assert( + equal(zix_path_preferred(NULL, "\\a\\//b\\/c\\"), "\\a\\\\\\b\\\\c\\")); + assert(equal(zix_path_preferred(NULL, "\\a\\/b\\/c\\"), "\\a\\\\b\\\\c\\")); + assert(equal(zix_path_preferred(NULL, "\\a\\b\\c\\"), "\\a\\b\\c\\")); +#else + // Slash is the only separator + assert(equal(zix_path_preferred(NULL, "/"), "/")); + assert(equal(zix_path_preferred(NULL, "/."), "/.")); + assert(equal(zix_path_preferred(NULL, "//a"), "//a")); + assert(equal(zix_path_preferred(NULL, "//a/"), "//a/")); + assert(equal(zix_path_preferred(NULL, "//a//"), "//a//")); + assert(equal(zix_path_preferred(NULL, "/a//b/c/"), "/a//b/c/")); + assert(equal(zix_path_preferred(NULL, "/a/b/c/"), "/a/b/c/")); + assert(equal(zix_path_preferred(NULL, "\\"), "\\")); + assert(equal(zix_path_preferred(NULL, "\\a\\//b\\/c\\"), "\\a\\//b\\/c\\")); + assert(equal(zix_path_preferred(NULL, "\\a\\/b\\/c\\"), "\\a\\/b\\/c\\")); + assert(equal(zix_path_preferred(NULL, "\\a\\b\\c\\"), "\\a\\b\\c\\")); +#endif +} + +static void +test_path_lexically_normal(void) +{ + // Identities + assert(equal(zix_path_lexically_normal(NULL, ""), "")); + assert(equal(zix_path_lexically_normal(NULL, "."), ".")); + assert(equal(zix_path_lexically_normal(NULL, ".."), "..")); + assert(match(zix_path_lexically_normal(NULL, "../.."), "../..")); + assert(match(zix_path_lexically_normal(NULL, "/a/b/"), "/a/b/")); + assert(match(zix_path_lexically_normal(NULL, "/a/b/c"), "/a/b/c")); + assert(match(zix_path_lexically_normal(NULL, "a/b"), "a/b")); + + // "Out of bounds" leading dot-dot entries are removed + assert(match(zix_path_lexically_normal(NULL, "/../"), "/")); + assert(match(zix_path_lexically_normal(NULL, "/../.."), "/")); + assert(match(zix_path_lexically_normal(NULL, "/../../"), "/")); + + // Windows drive letters are preserved + assert(equal(zix_path_lexically_normal(NULL, "C:"), "C:")); + assert(equal(zix_path_lexically_normal(NULL, "C:a"), "C:a")); + assert(match(zix_path_lexically_normal(NULL, "C:/"), "C:/")); + assert(match(zix_path_lexically_normal(NULL, "C:/a"), "C:/a")); + assert(match(zix_path_lexically_normal(NULL, "C:/a/"), "C:/a/")); + assert(match(zix_path_lexically_normal(NULL, "C:/a/b"), "C:/a/b")); + assert(match(zix_path_lexically_normal(NULL, "C:a/"), "C:a/")); + + // Slashes are replaced with a single preferred-separator + assert(match(zix_path_lexically_normal(NULL, "/"), "/")); + assert(match(zix_path_lexically_normal(NULL, "//"), "/")); + assert(match(zix_path_lexically_normal(NULL, "///"), "/")); + assert(match(zix_path_lexically_normal(NULL, "///a///b/////"), "/a/b/")); + assert(match(zix_path_lexically_normal(NULL, "/a/b//c"), "/a/b/c")); + assert(match(zix_path_lexically_normal(NULL, "a///b"), "a/b")); + assert(match(zix_path_lexically_normal(NULL, "a//b"), "a/b")); + assert(match(zix_path_lexically_normal(NULL, "a/b"), "a/b")); + assert(match(zix_path_lexically_normal(NULL, "a/b/"), "a/b/")); + assert(match(zix_path_lexically_normal(NULL, "a/b//"), "a/b/")); + assert(match(zix_path_lexically_normal(NULL, "a/b///"), "a/b/")); + + // Dot directories are removed + assert(equal(zix_path_lexically_normal(NULL, "./.."), "..")); + assert(match(zix_path_lexically_normal(NULL, ".//a//.//./b/.//"), "a/b/")); + assert(match(zix_path_lexically_normal(NULL, "./a/././b/./"), "a/b/")); + assert(match(zix_path_lexically_normal(NULL, "/."), "/")); + assert(match(zix_path_lexically_normal(NULL, "/a/b/./"), "/a/b/")); + assert(match(zix_path_lexically_normal(NULL, "a/."), "a/")); + assert(match(zix_path_lexically_normal(NULL, "a/./b/."), "a/b/")); + assert(match(zix_path_lexically_normal(NULL, "a/b/."), "a/b/")); + assert(match(zix_path_lexically_normal(NULL, "a/b/./"), "a/b/")); + + // Real entries before a dot-dot entry are removed + assert(equal(zix_path_lexically_normal(NULL, "a/.."), ".")); + assert(equal(zix_path_lexically_normal(NULL, "a/../"), ".")); + assert(equal(zix_path_lexically_normal(NULL, "a/b/../.."), ".")); + assert(equal(zix_path_lexically_normal(NULL, "a/b/../../"), ".")); + assert(match(zix_path_lexically_normal(NULL, "/a/b/c/../"), "/a/b/")); + assert(match(zix_path_lexically_normal(NULL, "/a/b/c/../d"), "/a/b/d")); + assert(match(zix_path_lexically_normal(NULL, "/a/b/c/../d/"), "/a/b/d/")); + assert(match(zix_path_lexically_normal(NULL, "a/b/.."), "a/")); + assert(match(zix_path_lexically_normal(NULL, "a/b/../"), "a/")); + assert(match(zix_path_lexically_normal(NULL, "a/b/./.."), "a/")); + assert(match(zix_path_lexically_normal(NULL, "a/b/./../"), "a/")); + assert(match(zix_path_lexically_normal(NULL, "a/b/c/../.."), "a/")); + assert(match(zix_path_lexically_normal(NULL, "a/b/c/../../"), "a/")); + + // Both dot directories and dot-dot entries + assert(match(zix_path_lexically_normal(NULL, "a/.///b/../"), "a/")); + assert(match(zix_path_lexically_normal(NULL, "a/./b/.."), "a/")); + + // Dot-dot entries after a root are removed + assert(match(zix_path_lexically_normal(NULL, "/.."), "/")); + assert(match(zix_path_lexically_normal(NULL, "/../"), "/")); + assert(match(zix_path_lexically_normal(NULL, "/../.."), "/")); + assert(match(zix_path_lexically_normal(NULL, "/../../"), "/")); + assert(match(zix_path_lexically_normal(NULL, "/../a"), "/a")); + assert(match(zix_path_lexically_normal(NULL, "/../a/../.."), "/")); + assert(match(zix_path_lexically_normal(NULL, "/a/../.."), "/")); + + // No trailing separator after a trailing dot-dot entry + assert(equal(zix_path_lexically_normal(NULL, "../"), "..")); + assert(match(zix_path_lexically_normal(NULL, "../../"), "../..")); + assert(match(zix_path_lexically_normal(NULL, "a/../b/../..///"), "..")); + assert( + match(zix_path_lexically_normal(NULL, "a/../b/..//..///../"), "../..")); + + // Effectively empty paths are a dot + assert(equal(zix_path_lexically_normal(NULL, "."), ".")); + assert(equal(zix_path_lexically_normal(NULL, "./"), ".")); + assert(equal(zix_path_lexically_normal(NULL, "./."), ".")); + assert(equal(zix_path_lexically_normal(NULL, "a/.."), ".")); + +#ifdef _WIN32 + + // Paths like "C:/filename" have a drive letter + assert(equal(zix_path_lexically_normal(NULL, "C:/a\\b"), "C:\\a\\b")); + assert(equal(zix_path_lexically_normal(NULL, "C:\\"), "C:\\")); + assert(equal(zix_path_lexically_normal(NULL, "C:\\a"), "C:\\a")); + assert(equal(zix_path_lexically_normal(NULL, "C:\\a/"), "C:\\a\\")); + assert(equal(zix_path_lexically_normal(NULL, "C:\\a\\"), "C:\\a\\")); + assert(equal(zix_path_lexically_normal(NULL, "C:a\\"), "C:a\\")); + + // Paths like "//host/dir/" have a network root + assert(equal(zix_path_lexically_normal(NULL, "//a/"), "\\\\a\\")); + assert(equal(zix_path_lexically_normal(NULL, "//a/.."), "\\\\a\\")); + assert(equal(zix_path_lexically_normal(NULL, "//a/b/"), "\\\\a\\b\\")); + assert(equal(zix_path_lexically_normal(NULL, "//a/b/."), "\\\\a\\b\\")); + +#else + + // "C:" is just a strange directory or file name prefix + assert(equal(zix_path_lexically_normal(NULL, "C:/a\\b"), "C:/a\\b")); + assert(equal(zix_path_lexically_normal(NULL, "C:\\"), "C:\\")); + assert(equal(zix_path_lexically_normal(NULL, "C:\\a"), "C:\\a")); + assert(equal(zix_path_lexically_normal(NULL, "C:\\a/"), "C:\\a/")); + assert(equal(zix_path_lexically_normal(NULL, "C:\\a\\"), "C:\\a\\")); + assert(equal(zix_path_lexically_normal(NULL, "C:a\\"), "C:a\\")); + + // Paths like "//host/dir/" have redundant leading slashes + assert(equal(zix_path_lexically_normal(NULL, "//a/"), "/a/")); + assert(equal(zix_path_lexically_normal(NULL, "//a/.."), "/")); + assert(equal(zix_path_lexically_normal(NULL, "//a/b/"), "/a/b/")); + assert(equal(zix_path_lexically_normal(NULL, "//a/b/."), "/a/b/")); + +#endif +} + +static void +test_path_lexically_relative(void) +{ + assert(equal(zix_path_lexically_relative(NULL, "", ""), ".")); + assert(equal(zix_path_lexically_relative(NULL, "", "."), ".")); + assert(equal(zix_path_lexically_relative(NULL, ".", ""), ".")); + assert(equal(zix_path_lexically_relative(NULL, ".", "."), ".")); + assert(equal(zix_path_lexically_relative(NULL, "//base", "a"), NULL)); + assert(equal(zix_path_lexically_relative(NULL, "//host", "//host"), ".")); + assert(equal(zix_path_lexically_relative(NULL, "//host", "a"), NULL)); + assert(equal(zix_path_lexically_relative(NULL, "//host/", "//host/"), ".")); + assert(equal(zix_path_lexically_relative(NULL, "//host/", "a"), NULL)); + assert( + equal(zix_path_lexically_relative(NULL, "//host/a/b", "//host/a/b"), ".")); + assert(equal(zix_path_lexically_relative(NULL, "/a/b", "/a/"), "b")); + assert(equal(zix_path_lexically_relative(NULL, "C:/a/b", "C:/a/"), "b")); + assert(equal(zix_path_lexically_relative(NULL, "a", "/"), NULL)); + assert(equal(zix_path_lexically_relative(NULL, "a", "//host"), NULL)); + assert(equal(zix_path_lexically_relative(NULL, "a", "//host/"), NULL)); + assert(equal(zix_path_lexically_relative(NULL, "a", "a"), ".")); + assert(equal(zix_path_lexically_relative(NULL, "a/b", "/a/b"), NULL)); + assert(equal(zix_path_lexically_relative(NULL, "a/b", "a/b"), ".")); + assert(equal(zix_path_lexically_relative(NULL, "a/b/c", "a"), "b/c")); + assert(equal(zix_path_lexically_relative(NULL, "a/b/c", "a/b/c"), ".")); + assert(equal(zix_path_lexically_relative(NULL, "a/b/c/", "a/b/c/"), ".")); + assert(match(zix_path_lexically_relative(NULL, "../", "../"), ".")); + assert(match(zix_path_lexically_relative(NULL, "../", "./"), "../")); + assert(match(zix_path_lexically_relative(NULL, "../", "a"), "../../")); + assert( + match(zix_path_lexically_relative(NULL, "../../a", "../b"), "../../a")); + assert(match(zix_path_lexically_relative(NULL, "../a", "../b"), "../a")); + assert(match(zix_path_lexically_relative(NULL, "/a", "/b/c/"), "../../a")); + assert(match(zix_path_lexically_relative(NULL, "/a/b/c", "/a/b/d/"), "../c")); + assert( + match(zix_path_lexically_relative(NULL, "/a/b/c", "/a/b/d/e/"), "../../c")); + assert(match(zix_path_lexically_relative(NULL, "/a/b/c", "/a/d"), "../b/c")); + assert(match(zix_path_lexically_relative(NULL, "/a/d", "/a/b/c"), "../../d")); + assert( + match(zix_path_lexically_relative(NULL, "C:/D/", "C:/D/F.txt"), "../")); + assert(match(zix_path_lexically_relative(NULL, "C:/D/F", "C:/D/S/"), "../F")); + assert(match(zix_path_lexically_relative(NULL, "C:/D/F", "C:/G"), "../D/F")); + assert( + match(zix_path_lexically_relative(NULL, "C:/D/F.txt", "C:/D/"), "F.txt")); + assert(match(zix_path_lexically_relative(NULL, "C:/E", "C:/D/G"), "../../E")); + assert( + match(zix_path_lexically_relative(NULL, "C:/a", "C:/b/c/"), "../../a")); + assert( + match(zix_path_lexically_relative(NULL, "C:/a/b/c", "C:/a/b/d/"), "../c")); + assert(match(zix_path_lexically_relative(NULL, "a/b", "c/d"), "../../a/b")); + assert(match(zix_path_lexically_relative(NULL, "a/b/c", "../"), NULL)); + assert(match(zix_path_lexically_relative(NULL, "a/b/c", "../../"), NULL)); + assert( + match(zix_path_lexically_relative(NULL, "a/b/c", "a/b/c/x/y"), "../..")); + +#ifdef _WIN32 + assert(equal(zix_path_lexically_relative(NULL, "/", "a"), "/")); + assert(equal(zix_path_lexically_relative(NULL, "//host", "//host/"), NULL)); + assert(equal(zix_path_lexically_relative(NULL, "//host/", "//host"), "/")); + assert(equal(zix_path_lexically_relative(NULL, "//host/", "/a"), NULL)); + assert(equal(zix_path_lexically_relative(NULL, "/a", "//host"), NULL)); + assert(equal(zix_path_lexically_relative(NULL, "/a", "//host/"), NULL)); + assert(equal(zix_path_lexically_relative(NULL, "C:/D/", "C:F.txt"), NULL)); + assert(equal(zix_path_lexically_relative(NULL, "C:/D/S/", "F.txt"), NULL)); + assert(equal(zix_path_lexically_relative(NULL, "C:F", "C:/D/"), NULL)); + assert(equal(zix_path_lexically_relative(NULL, "C:F", "D:G"), NULL)); + assert(equal(zix_path_lexically_relative(NULL, "F", "C:/D/S/"), NULL)); + assert(match(zix_path_lexically_relative(NULL, "C:../", "C:../"), ".")); + assert(match(zix_path_lexically_relative(NULL, "C:../", "C:./"), "../")); + assert(match(zix_path_lexically_relative(NULL, "C:../", "C:a"), "../../")); + assert( + match(zix_path_lexically_relative(NULL, "C:../../a", "C:../b"), "../../a")); + assert(match(zix_path_lexically_relative(NULL, "C:../a", "C:../b"), "../a")); + assert(match(zix_path_lexically_relative(NULL, "C:a/b/c", "C:../"), NULL)); + assert(match(zix_path_lexically_relative(NULL, "C:a/b/c", "C:../../"), NULL)); +#else + assert(equal(zix_path_lexically_relative(NULL, "/", "a"), NULL)); + assert(equal(zix_path_lexically_relative(NULL, "//host", "//host/"), ".")); + assert(equal(zix_path_lexically_relative(NULL, "//host/", "//host"), ".")); + assert(equal(zix_path_lexically_relative(NULL, "//host/", "/a"), "../host/")); + assert(equal(zix_path_lexically_relative(NULL, "/a", "//host"), "../a")); + assert(equal(zix_path_lexically_relative(NULL, "/a", "//host/"), "../a")); + assert( + equal(zix_path_lexically_relative(NULL, "C:/D/", "C:F.txt"), "../C:/D/")); + assert( + equal(zix_path_lexically_relative(NULL, "C:/D/S/", "F.txt"), "../C:/D/S/")); + assert(equal(zix_path_lexically_relative(NULL, "C:F", "C:/D/"), "../../C:F")); + assert(equal(zix_path_lexically_relative(NULL, "C:F", "D:G"), "../C:F")); + assert( + equal(zix_path_lexically_relative(NULL, "F", "C:/D/S/"), "../../../F")); + assert(match(zix_path_lexically_relative(NULL, "C:../", "C:../"), ".")); + assert(match(zix_path_lexically_relative(NULL, "C:../", "C:./"), "../C:../")); + assert(match(zix_path_lexically_relative(NULL, "C:../", "C:a"), "../C:../")); + assert( + match(zix_path_lexically_relative(NULL, "C:../../a", "C:../b"), "../../a")); + assert(match(zix_path_lexically_relative(NULL, "C:../a", "C:../b"), "../a")); + assert( + match(zix_path_lexically_relative(NULL, "C:a/b/c", "C:../"), "../C:a/b/c")); + assert( + match(zix_path_lexically_relative(NULL, "C:a/b/c", "C:../../"), "C:a/b/c")); +#endif +} + +static void +test_path_root_name(void) +{ + // Relative paths with no root + assert(view_equal(zix_path_root_name(""), NULL)); + assert(view_equal(zix_path_root_name("."), NULL)); + assert(view_equal(zix_path_root_name(".."), NULL)); + assert(view_equal(zix_path_root_name("../"), NULL)); + assert(view_equal(zix_path_root_name("./"), NULL)); + assert(view_equal(zix_path_root_name("NONDRIVE:"), NULL)); + assert(view_equal(zix_path_root_name("a"), NULL)); + assert(view_equal(zix_path_root_name("a/"), NULL)); + assert(view_equal(zix_path_root_name("a/."), NULL)); + assert(view_equal(zix_path_root_name("a/.."), NULL)); + assert(view_equal(zix_path_root_name("a/../"), NULL)); + assert(view_equal(zix_path_root_name("a/../b"), NULL)); + assert(view_equal(zix_path_root_name("a/./"), NULL)); + assert(view_equal(zix_path_root_name("a/./b"), NULL)); + assert(view_equal(zix_path_root_name("a/b"), NULL)); + + // Absolute paths with a POSIX-style root + assert(view_equal(zix_path_root_name("/"), NULL)); + assert(view_equal(zix_path_root_name("/."), NULL)); + assert(view_equal(zix_path_root_name("/.."), NULL)); + assert(view_equal(zix_path_root_name("//"), NULL)); + assert(view_equal(zix_path_root_name("///a///"), NULL)); + assert(view_equal(zix_path_root_name("///a///b"), NULL)); + assert(view_equal(zix_path_root_name("/a"), NULL)); + assert(view_equal(zix_path_root_name("/a/"), NULL)); + assert(view_equal(zix_path_root_name("/a//b"), NULL)); + +#ifdef _WIN32 + + // Paths like "C:/filename" have a drive letter + assert(view_equal(zix_path_root_name("C:"), "C:")); + assert(view_equal(zix_path_root_name("C:/"), "C:")); + assert(view_equal(zix_path_root_name("C:/a"), "C:")); + assert(view_equal(zix_path_root_name("C:/a/"), "C:")); + assert(view_equal(zix_path_root_name("C:/a/b"), "C:")); + assert(view_equal(zix_path_root_name("C:a"), "C:")); + assert(view_equal(zix_path_root_name("C:a/"), "C:")); + + // Paths like "//host/" are network roots + assert(view_equal(zix_path_root_name("//host"), "//host")); + assert(view_equal(zix_path_root_name("//host/"), "//host")); + assert(view_equal(zix_path_root_name("//host/a"), "//host")); + + // Backslash is a directory separator + assert(view_equal(zix_path_root_name("C:/a\\b"), "C:")); + assert(view_equal(zix_path_root_name("C:\\"), "C:")); + assert(view_equal(zix_path_root_name("C:\\a"), "C:")); + assert(view_equal(zix_path_root_name("C:\\a/"), "C:")); + assert(view_equal(zix_path_root_name("C:\\a\\"), "C:")); + assert(view_equal(zix_path_root_name("C:a\\"), "C:")); + +#else + + // "C:" is just a strange directory or file name prefix + assert(view_equal(zix_path_root_name("C:"), NULL)); + assert(view_equal(zix_path_root_name("C:/"), NULL)); + assert(view_equal(zix_path_root_name("C:/a"), NULL)); + assert(view_equal(zix_path_root_name("C:/a/"), NULL)); + assert(view_equal(zix_path_root_name("C:/a/b"), NULL)); + assert(view_equal(zix_path_root_name("C:a"), NULL)); + assert(view_equal(zix_path_root_name("C:a/"), NULL)); + + // Paths like "//host/" have redundant leading slashes + assert(view_equal(zix_path_root_name("//host"), NULL)); + assert(view_equal(zix_path_root_name("//host/"), NULL)); + assert(view_equal(zix_path_root_name("//host/a"), NULL)); + + // Backslash is a character in a directory or file name + assert(view_equal(zix_path_root_name("C:/a\\b"), NULL)); + assert(view_equal(zix_path_root_name("C:\\"), NULL)); + assert(view_equal(zix_path_root_name("C:\\a"), NULL)); + assert(view_equal(zix_path_root_name("C:\\a/"), NULL)); + assert(view_equal(zix_path_root_name("C:\\a\\"), NULL)); + assert(view_equal(zix_path_root_name("C:a\\"), NULL)); + +#endif +} + +static void +test_path_root(void) +{ + // Relative paths with no root + assert(view_equal(zix_path_root_path(""), NULL)); + assert(view_equal(zix_path_root_path("."), NULL)); + assert(view_equal(zix_path_root_path(".."), NULL)); + assert(view_equal(zix_path_root_path("../"), NULL)); + assert(view_equal(zix_path_root_path("./"), NULL)); + assert(view_equal(zix_path_root_path("NONDRIVE:"), NULL)); + assert(view_equal(zix_path_root_path("a"), NULL)); + assert(view_equal(zix_path_root_path("a/"), NULL)); + assert(view_equal(zix_path_root_path("a/."), NULL)); + assert(view_equal(zix_path_root_path("a/.."), NULL)); + assert(view_equal(zix_path_root_path("a/../"), NULL)); + assert(view_equal(zix_path_root_path("a/../b"), NULL)); + assert(view_equal(zix_path_root_path("a/./"), NULL)); + assert(view_equal(zix_path_root_path("a/./b"), NULL)); + assert(view_equal(zix_path_root_path("a/b"), NULL)); + + // Absolute paths with a POSIX-style root + assert(view_equal(zix_path_root_path("/"), "/")); + assert(view_equal(zix_path_root_path("/."), "/")); + assert(view_equal(zix_path_root_path("/.."), "/")); + assert(view_equal(zix_path_root_path("//"), "/")); + assert(view_equal(zix_path_root_path("///a///"), "/")); + assert(view_equal(zix_path_root_path("///a///b"), "/")); + assert(view_equal(zix_path_root_path("/a"), "/")); + assert(view_equal(zix_path_root_path("/a/"), "/")); + assert(view_equal(zix_path_root_path("/a//b"), "/")); + +#ifdef _WIN32 + + // Paths like "C:/filename" have a drive letter + assert(view_equal(zix_path_root_path("C:"), "C:")); + assert(view_equal(zix_path_root_path("C:/"), "C:/")); + assert(view_equal(zix_path_root_path("C:/a"), "C:/")); + assert(view_equal(zix_path_root_path("C:/a/"), "C:/")); + assert(view_equal(zix_path_root_path("C:/a/b"), "C:/")); + assert(view_equal(zix_path_root_path("C:a"), "C:")); + assert(view_equal(zix_path_root_path("C:a/"), "C:")); + + // Paths like "//host/" are network roots + assert(view_equal(zix_path_root_path("//host"), "//host")); + assert(view_equal(zix_path_root_path("//host/"), "//host/")); + assert(view_equal(zix_path_root_path("//host/a"), "//host/")); + + // Backslash is a directory separator + assert(view_equal(zix_path_root_path("C:/a\\b"), "C:/")); + assert(view_equal(zix_path_root_path("C:\\"), "C:\\")); + assert(view_equal(zix_path_root_path("C:\\a"), "C:\\")); + assert(view_equal(zix_path_root_path("C:\\a/"), "C:\\")); + assert(view_equal(zix_path_root_path("C:\\a\\"), "C:\\")); + assert(view_equal(zix_path_root_path("C:a\\"), "C:")); + +#else + + // "C:" is just a strange directory or file name prefix + assert(view_equal(zix_path_root_path("C:"), NULL)); + assert(view_equal(zix_path_root_path("C:/"), NULL)); + assert(view_equal(zix_path_root_path("C:/a"), NULL)); + assert(view_equal(zix_path_root_path("C:/a/"), NULL)); + assert(view_equal(zix_path_root_path("C:/a/b"), NULL)); + assert(view_equal(zix_path_root_path("C:a"), NULL)); + assert(view_equal(zix_path_root_path("C:a/"), NULL)); + + // Paths like "//host/" have redundant leading slashes + assert(view_equal(zix_path_root_path("//host"), "/")); + assert(view_equal(zix_path_root_path("//host/"), "/")); + assert(view_equal(zix_path_root_path("//host/a"), "/")); + + // Backslash is a character in a directory or file name + assert(view_equal(zix_path_root_path("C:/a\\b"), NULL)); + assert(view_equal(zix_path_root_path("C:\\"), NULL)); + assert(view_equal(zix_path_root_path("C:\\a"), NULL)); + assert(view_equal(zix_path_root_path("C:\\a/"), NULL)); + assert(view_equal(zix_path_root_path("C:\\a\\"), NULL)); + assert(view_equal(zix_path_root_path("C:a\\"), NULL)); + +#endif +} + +static void +test_path_parent(void) +{ + // Absolute paths + assert(view_equal(zix_path_parent_path("/"), "/")); + assert(view_equal(zix_path_parent_path("/."), "/")); + assert(view_equal(zix_path_parent_path("/.."), "/")); + assert(view_equal(zix_path_parent_path("//"), "/")); + assert(view_equal(zix_path_parent_path("/a"), "/")); + assert(view_equal(zix_path_parent_path("/a/"), "/a")); + assert(view_equal(zix_path_parent_path("/a//b"), "/a")); + + // Relative paths with no parent + assert(view_equal(zix_path_parent_path(""), NULL)); + assert(view_equal(zix_path_parent_path("."), NULL)); + assert(view_equal(zix_path_parent_path(".."), NULL)); + assert(view_equal(zix_path_parent_path("NONDRIVE:"), NULL)); + assert(view_equal(zix_path_parent_path("a"), NULL)); + + // Relative paths with a parent + assert(view_equal(zix_path_parent_path("../"), "..")); + assert(view_equal(zix_path_parent_path("./"), ".")); + assert(view_equal(zix_path_parent_path("a/"), "a")); + assert(view_equal(zix_path_parent_path("a/b"), "a")); + + // Superfluous leading and trailing separators + assert(view_equal(zix_path_parent_path("///a///"), "/a")); + assert(view_equal(zix_path_parent_path("///a///b"), "/a")); + + // Relative paths with dot and dot-dot entries + assert(view_equal(zix_path_parent_path("a/."), "a")); + assert(view_equal(zix_path_parent_path("a/.."), "a")); + assert(view_equal(zix_path_parent_path("a/../"), "a/..")); + assert(view_equal(zix_path_parent_path("a/../b"), "a/..")); + assert(view_equal(zix_path_parent_path("a/./"), "a/.")); + assert(view_equal(zix_path_parent_path("a/./b"), "a/.")); + +#ifdef _WIN32 + + // Paths like "C:/filename" have a drive letter + assert(view_equal(zix_path_parent_path("C:"), "C:")); + assert(view_equal(zix_path_parent_path("C:/"), "C:/")); + assert(view_equal(zix_path_parent_path("C:/a"), "C:/")); + assert(view_equal(zix_path_parent_path("C:/a/"), "C:/a")); + assert(view_equal(zix_path_parent_path("C:/a/b"), "C:/a")); + assert(view_equal(zix_path_parent_path("C:/a\\b"), "C:/a")); + assert(view_equal(zix_path_parent_path("C:\\"), "C:\\")); + assert(view_equal(zix_path_parent_path("C:\\a"), "C:\\")); + assert(view_equal(zix_path_parent_path("C:\\a/"), "C:\\a")); + assert(view_equal(zix_path_parent_path("C:\\a\\"), "C:\\a")); + assert(view_equal(zix_path_parent_path("C:a"), "C:")); + assert(view_equal(zix_path_parent_path("C:a/"), "C:a")); + assert(view_equal(zix_path_parent_path("C:a\\"), "C:a")); + + // Paths like "//host/" are network shares + assert(view_equal(zix_path_parent_path("//host"), "//host")); + assert(view_equal(zix_path_parent_path("//host/"), "//host/")); + assert(view_equal(zix_path_parent_path("//host/a"), "//host/")); + +#else + + // "C:" is just a strange directory or file name prefix + assert(view_equal(zix_path_parent_path("C:"), NULL)); + assert(view_equal(zix_path_parent_path("C:/"), "C:")); + assert(view_equal(zix_path_parent_path("C:/a"), "C:")); + assert(view_equal(zix_path_parent_path("C:/a/"), "C:/a")); + assert(view_equal(zix_path_parent_path("C:/a/b"), "C:/a")); + assert(view_equal(zix_path_parent_path("C:/a\\b"), "C:")); + assert(view_equal(zix_path_parent_path("C:\\"), NULL)); + assert(view_equal(zix_path_parent_path("C:\\a"), NULL)); + assert(view_equal(zix_path_parent_path("C:\\a/"), "C:\\a")); + assert(view_equal(zix_path_parent_path("C:\\a\\"), NULL)); + assert(view_equal(zix_path_parent_path("C:a"), NULL)); + assert(view_equal(zix_path_parent_path("C:a/"), "C:a")); + assert(view_equal(zix_path_parent_path("C:a\\"), NULL)); + + // Paths like "//host/" have redundant leading slashes + assert(view_equal(zix_path_parent_path("//host"), "/")); + assert(view_equal(zix_path_parent_path("//host/"), "/host")); + assert(view_equal(zix_path_parent_path("//host/a"), "/host")); + +#endif +} + +static void +test_path_filename(void) +{ + // Cases from <https://en.cppreference.com/w/cpp/filesystem/path/filename> + assert(view_equal(zix_path_filename("."), ".")); + assert(view_equal(zix_path_filename(".."), "..")); + assert(view_equal(zix_path_filename("/"), NULL)); + assert(view_equal(zix_path_filename("/foo/."), ".")); + assert(view_equal(zix_path_filename("/foo/.."), "..")); + assert(view_equal(zix_path_filename("/foo/.bar"), ".bar")); + assert(view_equal(zix_path_filename("/foo/bar.txt"), "bar.txt")); + assert(view_equal(zix_path_filename("/foo/bar/"), NULL)); + + // Identities + assert(view_equal(zix_path_filename("."), ".")); + assert(view_equal(zix_path_filename(".."), "..")); + assert(view_equal(zix_path_filename("a"), "a")); + + // Absolute paths without filenames + assert(view_equal(zix_path_filename("/"), NULL)); + assert(view_equal(zix_path_filename("//"), NULL)); + assert(view_equal(zix_path_filename("///a///"), NULL)); + assert(view_equal(zix_path_filename("/a/"), NULL)); + + // Absolute paths with filenames + assert(view_equal(zix_path_filename("///a///b"), "b")); + assert(view_equal(zix_path_filename("/a"), "a")); + assert(view_equal(zix_path_filename("/a//b"), "b")); + + // Relative paths without filenames + assert(view_equal(zix_path_filename(""), NULL)); + assert(view_equal(zix_path_filename("../"), NULL)); + assert(view_equal(zix_path_filename("./"), NULL)); + assert(view_equal(zix_path_filename("a/"), NULL)); + + // Relative paths with filenames + assert(view_equal(zix_path_filename("a/b"), "b")); + + // Windows absolute network paths conveniently work generically + assert(view_equal(zix_path_filename("//host/"), NULL)); + assert(view_equal(zix_path_filename("//host/a"), "a")); + + // Paths with dot and dot-dot entries + assert(view_equal(zix_path_filename("/."), ".")); + assert(view_equal(zix_path_filename("/.."), "..")); + assert(view_equal(zix_path_filename("a/."), ".")); + assert(view_equal(zix_path_filename("a/.."), "..")); + assert(view_equal(zix_path_filename("a/../b"), "b")); + assert(view_equal(zix_path_filename("a/./b"), "b")); + + // Paths with colons (maybe drive letters) that conveniently work generically + assert(view_equal(zix_path_filename("C:/"), NULL)); + assert(view_equal(zix_path_filename("C:/a"), "a")); + assert(view_equal(zix_path_filename("C:/a/"), NULL)); + assert(view_equal(zix_path_filename("C:/a/b"), "b")); + assert(view_equal(zix_path_filename("C:a/"), NULL)); + assert(view_equal(zix_path_filename("NONDRIVE:"), "NONDRIVE:")); + +#ifdef _WIN32 + + // Relative paths can have a drive letter like "C:file" + assert(view_equal(zix_path_filename("C:"), NULL)); + assert(view_equal(zix_path_filename("C:a"), "a")); + assert(view_equal(zix_path_filename("C:a\\"), NULL)); + + // Paths like "//host" are network roots + assert(view_equal(zix_path_filename("//host"), NULL)); + + // Backslash is a directory separator + assert(view_equal(zix_path_filename("C:/a\\b"), "b")); + assert(view_equal(zix_path_filename("C:\\"), NULL)); + assert(view_equal(zix_path_filename("C:\\a"), "a")); + assert(view_equal(zix_path_filename("C:\\a/"), NULL)); + assert(view_equal(zix_path_filename("C:\\a\\"), NULL)); + assert(view_equal(zix_path_filename("C:\\a\\b"), "b")); + assert(view_equal(zix_path_filename("a\\b"), "b")); + +#else + + // "C:" is just a strange directory or file name prefix + assert(view_equal(zix_path_filename("C:"), "C:")); + assert(view_equal(zix_path_filename("C:a"), "C:a")); + assert(view_equal(zix_path_filename("C:a\\"), "C:a\\")); + + // Redundant reading slashes are ignored + assert(view_equal(zix_path_filename("//host"), "host")); + + // Backslash is just a strange character in a directory or file name + assert(view_equal(zix_path_filename("C:/a\\b"), "a\\b")); + assert(view_equal(zix_path_filename("C:\\"), "C:\\")); + assert(view_equal(zix_path_filename("C:\\a"), "C:\\a")); + assert(view_equal(zix_path_filename("C:\\a/"), NULL)); + assert(view_equal(zix_path_filename("C:\\a\\"), "C:\\a\\")); + assert(view_equal(zix_path_filename("C:\\a\\b"), "C:\\a\\b")); + assert(view_equal(zix_path_filename("a\\b"), "a\\b")); + +#endif +} + +static void +test_path_stem(void) +{ + assert(view_equal(zix_path_stem(""), NULL)); + assert(view_equal(zix_path_stem("."), ".")); + assert(view_equal(zix_path_stem(".."), "..")); + assert(view_equal(zix_path_stem(".a"), ".a")); + assert(view_equal(zix_path_stem(".hidden"), ".hidden")); + assert(view_equal(zix_path_stem(".hidden.txt"), ".hidden")); + assert(view_equal(zix_path_stem("/"), NULL)); + assert(view_equal(zix_path_stem("//host/name"), "name")); + assert(view_equal(zix_path_stem("/a."), "a")); + assert(view_equal(zix_path_stem("/a.txt"), "a")); + assert(view_equal(zix_path_stem("/a/."), ".")); + assert(view_equal(zix_path_stem("/a/.."), "..")); + assert(view_equal(zix_path_stem("/a/.hidden"), ".hidden")); + assert(view_equal(zix_path_stem("/a/b."), "b")); + assert(view_equal(zix_path_stem("/a/b.tar.gz"), "b.tar")); + assert(view_equal(zix_path_stem("/a/b.txt"), "b")); + assert(view_equal(zix_path_stem("/a/b/.hidden"), ".hidden")); + assert(view_equal(zix_path_stem("C:/name"), "name")); + assert(view_equal(zix_path_stem("C:dir/name"), "name")); + assert(view_equal(zix_path_stem("a"), "a")); + assert(view_equal(zix_path_stem("a."), "a")); + assert(view_equal(zix_path_stem("a..txt"), "a.")); + assert(view_equal(zix_path_stem("a.txt"), "a")); + assert(view_equal(zix_path_stem("a/."), ".")); + assert(view_equal(zix_path_stem("a/.."), "..")); + assert(view_equal(zix_path_stem("a/b."), "b")); + assert(view_equal(zix_path_stem("a/b.tar.gz"), "b.tar")); + assert(view_equal(zix_path_stem("a/b.txt"), "b")); + +#ifdef _WIN32 + assert(view_equal(zix_path_stem("C:."), ".")); + assert(view_equal(zix_path_stem("C:.a"), ".a")); + assert(view_equal(zix_path_stem("C:a"), "a")); +#else + assert(view_equal(zix_path_stem("C:."), "C:")); + assert(view_equal(zix_path_stem("C:.a"), "C:")); + assert(view_equal(zix_path_stem("C:a"), "C:a")); +#endif +} + +static void +test_path_extension(void) +{ + assert(view_equal(zix_path_extension(""), NULL)); + assert(view_equal(zix_path_extension("."), NULL)); + assert(view_equal(zix_path_extension(".."), NULL)); + assert(view_equal(zix_path_extension(".a"), NULL)); + assert(view_equal(zix_path_extension(".hidden"), NULL)); + assert(view_equal(zix_path_extension(".hidden.txt"), ".txt")); + assert(view_equal(zix_path_extension("/"), NULL)); + assert(view_equal(zix_path_extension("/a."), ".")); + assert(view_equal(zix_path_extension("/a.txt"), ".txt")); + assert(view_equal(zix_path_extension("/a/."), NULL)); + assert(view_equal(zix_path_extension("/a/.."), NULL)); + assert(view_equal(zix_path_extension("/a/.hidden"), NULL)); + assert(view_equal(zix_path_extension("/a/b."), ".")); + assert(view_equal(zix_path_extension("/a/b.tar.gz"), ".gz")); + assert(view_equal(zix_path_extension("/a/b.txt"), ".txt")); + assert(view_equal(zix_path_extension("/a/b/.hidden"), NULL)); + assert(view_equal(zix_path_extension("C:/.hidden.txt"), ".txt")); + assert(view_equal(zix_path_extension("C:a.txt"), ".txt")); + assert(view_equal(zix_path_extension("a"), NULL)); + assert(view_equal(zix_path_extension("a."), ".")); + assert(view_equal(zix_path_extension("a..txt"), ".txt")); + assert(view_equal(zix_path_extension("a.tar.gz"), ".gz")); + assert(view_equal(zix_path_extension("a/."), NULL)); + assert(view_equal(zix_path_extension("a/.."), NULL)); + assert(view_equal(zix_path_extension("a/b."), ".")); + assert(view_equal(zix_path_extension("a/b.tar.gz"), ".gz")); + assert(view_equal(zix_path_extension("a/b.txt"), ".txt")); + +#ifdef _WIN32 + assert(view_equal(zix_path_extension("C:."), NULL)); + assert(view_equal(zix_path_extension("C:/.hidden"), NULL)); + assert(view_equal(zix_path_extension("C:/a.txt"), ".txt")); +#else + assert(view_equal(zix_path_extension("C:."), ".")); + assert(view_equal(zix_path_extension("C:/.hidden"), NULL)); + assert(view_equal(zix_path_extension("C:/a.txt"), ".txt")); +#endif +} + +static void +test_path_is_absolute(void) +{ + assert(!zix_path_is_absolute(".")); + assert(!zix_path_is_absolute("..")); + assert(!zix_path_is_absolute("../")); + assert(!zix_path_is_absolute("../a")); + assert(!zix_path_is_absolute("../a/")); + assert(!zix_path_is_absolute("a")); + assert(!zix_path_is_absolute("a/b")); + assert(!zix_path_is_relative("//host/a")); + assert(zix_path_is_absolute("//host/a")); + assert(zix_path_is_relative(".")); + assert(zix_path_is_relative("..")); + assert(zix_path_is_relative("../")); + assert(zix_path_is_relative("../a")); + assert(zix_path_is_relative("../a/")); + assert(zix_path_is_relative("a")); + assert(zix_path_is_relative("a/b")); + +#ifdef _WIN32 + // Paths starting with root names are absolute + assert(!zix_path_is_absolute("/")); + assert(!zix_path_is_absolute("/a")); + assert(!zix_path_is_absolute("/a/b")); + assert(!zix_path_is_relative("C:/a/b")); + assert(!zix_path_is_relative("C:\\a\\b")); + assert(!zix_path_is_relative("D:/a/b")); + assert(!zix_path_is_relative("D:\\a\\b")); + assert(zix_path_is_absolute("C:/a/b")); + assert(zix_path_is_absolute("C:\\a\\b")); + assert(zix_path_is_absolute("D:/a/b")); + assert(zix_path_is_absolute("D:\\a\\b")); + assert(zix_path_is_relative("/")); + assert(zix_path_is_relative("/a")); + assert(zix_path_is_relative("/a/b")); +#else + // Paths starting with slashes are absolute + assert(!zix_path_is_absolute("C:/a/b")); + assert(!zix_path_is_absolute("C:\\a\\b")); + assert(!zix_path_is_absolute("D:/a/b")); + assert(!zix_path_is_absolute("D:\\a\\b")); + assert(!zix_path_is_relative("/")); + assert(!zix_path_is_relative("/a")); + assert(!zix_path_is_relative("/a/b")); + assert(zix_path_is_absolute("/")); + assert(zix_path_is_absolute("/a")); + assert(zix_path_is_absolute("/a/b")); + assert(zix_path_is_relative("C:/a/b")); + assert(zix_path_is_relative("C:\\a\\b")); + assert(zix_path_is_relative("D:/a/b")); + assert(zix_path_is_relative("D:\\a\\b")); + +#endif +} + +int +main(void) +{ + test_path_root_name(); + test_path_root(); + test_path_parent(); + test_path_filename(); + test_path_stem(); + test_path_extension(); + test_path_is_absolute(); + + test_path_join(); + test_path_preferred(); + test_path_lexically_normal(); + test_path_lexically_relative(); + + return 0; +} |