summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/cpp/.clang-tidy13
-rw-r--r--test/cpp/test_path_std.cpp496
-rw-r--r--test/headers/test_headers.c1
-rw-r--r--test/test_path.c948
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;
+}