summaryrefslogtreecommitdiffstats
path: root/test/cpp
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2022-10-23 13:41:15 -0400
committerDavid Robillard <d@drobilla.net>2022-10-23 13:46:58 -0400
commitc886d489576cd0bc33d7d22d81981c794067946f (patch)
treef43c8d872401ed80b37f974516d9e9f2fee9d765 /test/cpp
parentf95a698b94069ed03f6a8f2d0f7eb089d66c91ef (diff)
downloadzix-c886d489576cd0bc33d7d22d81981c794067946f.tar.gz
zix-c886d489576cd0bc33d7d22d81981c794067946f.tar.bz2
zix-c886d489576cd0bc33d7d22d81981c794067946f.zip
Add path API
Diffstat (limited to 'test/cpp')
-rw-r--r--test/cpp/.clang-tidy13
-rw-r--r--test/cpp/test_path_std.cpp496
2 files changed, 509 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)));
+ }
+}