summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2022-10-23 13:41:27 -0400
committerDavid Robillard <d@drobilla.net>2022-10-23 14:57:45 -0400
commitd8b960be46007f9c09356e526d3c2dcff4b186a5 (patch)
treecd40db8d5634e74f8795922b7ab19fc8c6507648 /test
parentc886d489576cd0bc33d7d22d81981c794067946f (diff)
downloadzix-d8b960be46007f9c09356e526d3c2dcff4b186a5.tar.gz
zix-d8b960be46007f9c09356e526d3c2dcff4b186a5.tar.bz2
zix-d8b960be46007f9c09356e526d3c2dcff4b186a5.zip
Add filesystem API
Diffstat (limited to 'test')
-rw-r--r--test/.clang-tidy1
-rw-r--r--test/headers/test_headers.c1
-rw-r--r--test/test_filesystem.c712
-rw-r--r--test/test_status.c4
4 files changed, 716 insertions, 2 deletions
diff --git a/test/.clang-tidy b/test/.clang-tidy
index 5722b69..16da00c 100644
--- a/test/.clang-tidy
+++ b/test/.clang-tidy
@@ -3,6 +3,7 @@
Checks: >
-*-magic-numbers,
+ -android-cloexec-fopen,
-bugprone-easily-swappable-parameters,
-cert-err33-c,
-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,
diff --git a/test/headers/test_headers.c b/test/headers/test_headers.c
index 9e5bddd..d62dc2e 100644
--- a/test/headers/test_headers.c
+++ b/test/headers/test_headers.c
@@ -7,6 +7,7 @@
#include "zix/btree.h" // IWYU pragma: keep
#include "zix/bump_allocator.h" // IWYU pragma: keep
#include "zix/digest.h" // IWYU pragma: keep
+#include "zix/filesystem.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
diff --git a/test/test_filesystem.c b/test/test_filesystem.c
new file mode 100644
index 0000000..b0ec2a4
--- /dev/null
+++ b/test/test_filesystem.c
@@ -0,0 +1,712 @@
+// Copyright 2020-2022 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#undef NDEBUG
+
+#include "zix/allocator.h"
+#include "zix/filesystem.h"
+#include "zix/path.h"
+#include "zix/status.h"
+#include "zix/string_view.h"
+
+#ifndef _WIN32
+# include <unistd.h>
+#endif
+
+#if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L
+# include <sys/socket.h>
+# include <sys/stat.h>
+# include <sys/un.h>
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void
+test_temp_directory_path(void)
+{
+ char* tmpdir = zix_temp_directory_path(NULL);
+
+ assert(zix_file_type(tmpdir) == ZIX_FILE_TYPE_DIRECTORY);
+
+ free(tmpdir);
+}
+
+static void
+test_current_path(void)
+{
+ char* cwd = zix_current_path(NULL);
+
+ assert(zix_file_type(cwd) == ZIX_FILE_TYPE_DIRECTORY);
+
+ free(cwd);
+}
+
+static char*
+create_temp_dir(const char* const name_pattern)
+{
+ char* const temp = zix_temp_directory_path(NULL);
+ char* const path_pattern = zix_path_join(NULL, temp, name_pattern);
+ char* const result = zix_create_temporary_directory(NULL, path_pattern);
+ free(path_pattern);
+ zix_free(NULL, temp);
+ return result;
+}
+
+static void
+test_canonical_path(void)
+{
+ assert(!zix_canonical_path(NULL, NULL));
+
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
+ assert(temp_dir);
+
+ char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
+ assert(file_path);
+
+ {
+ FILE* const f = fopen(file_path, "w");
+ fprintf(f, "test\n");
+ fclose(f);
+ }
+
+#ifndef _WIN32
+ // Test symlink resolution
+
+ char* const link_path = zix_path_join(NULL, temp_dir, "zix_test_link");
+
+ assert(!zix_create_symlink(file_path, link_path));
+
+ char* const real_file_path = zix_canonical_path(NULL, file_path);
+ char* const real_link_path = zix_canonical_path(NULL, link_path);
+
+ assert(real_file_path);
+ assert(real_link_path);
+ assert(!strcmp(real_file_path, real_link_path));
+
+ assert(!zix_remove(link_path));
+ free(real_link_path);
+ free(real_file_path);
+ free(link_path);
+#endif
+
+ // Test dot segment resolution
+
+ char* const parent_dir_1 = zix_path_join(NULL, temp_dir, "..");
+ assert(parent_dir_1);
+
+ const ZixStringView parent_view = zix_path_parent_path(temp_dir);
+ char* const parent_dir_2 = zix_string_view_copy(NULL, parent_view);
+ assert(parent_dir_2);
+ assert(parent_dir_2[0]);
+
+ char* const real_parent_dir_1 = zix_canonical_path(NULL, parent_dir_1);
+ char* const real_parent_dir_2 = zix_canonical_path(NULL, parent_dir_2);
+
+ assert(real_parent_dir_1);
+ assert(real_parent_dir_2);
+ assert(!strcmp(real_parent_dir_1, real_parent_dir_2));
+
+ // Test missing files
+
+ assert(!zix_canonical_path(NULL, "/presumuably/absent"));
+ assert(!zix_canonical_path(NULL, "/presumuably/absent/"));
+ assert(!zix_canonical_path(NULL, "presumuably_absent"));
+
+ // Clean everything up
+
+ assert(!zix_remove(file_path));
+ assert(!zix_remove(temp_dir));
+
+ free(real_parent_dir_2);
+ free(real_parent_dir_1);
+ free(parent_dir_2);
+ free(parent_dir_1);
+ free(file_path);
+ free(temp_dir);
+}
+
+static void
+test_file_type(void)
+{
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
+ assert(temp_dir);
+
+ char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
+ assert(file_path);
+ assert(zix_file_type(file_path) == ZIX_FILE_TYPE_NONE);
+
+ // Regular file
+ FILE* f = fopen(file_path, "w");
+ fprintf(f, "test\n");
+ fclose(f);
+ assert(zix_file_type(file_path) == ZIX_FILE_TYPE_REGULAR);
+ assert(!zix_remove(file_path));
+
+#if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L
+
+ // FIFO
+ if (!mkfifo(file_path, 0600)) {
+ assert(zix_file_type(file_path) == ZIX_FILE_TYPE_FIFO);
+ assert(!zix_remove(file_path));
+ }
+
+ // Socket
+ const int sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sock >= 0) {
+ const socklen_t addr_len = sizeof(struct sockaddr_un);
+ struct sockaddr_un* const addr = (struct sockaddr_un*)calloc(1, addr_len);
+
+ addr->sun_family = AF_UNIX;
+ strncpy(addr->sun_path, file_path, sizeof(addr->sun_path) - 1);
+
+ const int fd = bind(sock, (struct sockaddr*)addr, addr_len);
+ if (fd >= 0) {
+ assert(zix_file_type(file_path) == ZIX_FILE_TYPE_SOCKET);
+ assert(!zix_remove(file_path));
+ }
+ close(fd);
+ close(sock);
+ free(addr);
+ }
+
+#endif
+
+ assert(!zix_remove(temp_dir));
+ free(file_path);
+ free(temp_dir);
+}
+
+static void
+test_path_exists(void)
+{
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
+ char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
+ assert(temp_dir);
+ assert(file_path);
+
+ assert(zix_file_type(file_path) == ZIX_FILE_TYPE_NONE);
+
+ FILE* f = fopen(file_path, "w");
+ fprintf(f, "test\n");
+ fclose(f);
+
+ assert(zix_file_type(file_path) == ZIX_FILE_TYPE_REGULAR);
+
+ assert(!zix_remove(file_path));
+ assert(!zix_remove(temp_dir));
+
+ free(file_path);
+ free(temp_dir);
+}
+
+static void
+test_is_directory(void)
+{
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
+ char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
+ assert(temp_dir);
+ assert(file_path);
+
+ assert(zix_file_type(temp_dir) == ZIX_FILE_TYPE_DIRECTORY);
+ assert(zix_file_type(file_path) == ZIX_FILE_TYPE_NONE);
+
+ FILE* f = fopen(file_path, "w");
+ fprintf(f, "test\n");
+ fclose(f);
+
+ assert(zix_file_type(file_path) == ZIX_FILE_TYPE_REGULAR);
+
+ assert(!zix_remove(file_path));
+ assert(!zix_remove(temp_dir));
+
+ free(file_path);
+ free(temp_dir);
+}
+
+static int
+write_to_path(const char* const path, const char* const contents)
+{
+ FILE* const f = fopen(path, "w");
+ if (!f) {
+ return -1;
+ }
+
+ const size_t len = strlen(contents);
+ fwrite(contents, 1, len, f);
+
+ const int ret = fflush(f) ? errno : ferror(f) ? EBADF : 0;
+
+ return fclose(f) ? errno : ret;
+}
+
+static void
+test_copy_file(const char* const data_file_path)
+{
+ ZixStatus st = ZIX_STATUS_SUCCESS;
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
+ char* const tmp_file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
+ char* const copy_path = zix_path_join(NULL, temp_dir, "zix_test_copy");
+ assert(temp_dir);
+ assert(tmp_file_path);
+ assert(copy_path);
+
+ write_to_path(tmp_file_path, "test\n");
+
+ assert((st = zix_copy_file(NULL, tmp_file_path, "/does/not/exist", 0U)));
+ assert(zix_copy_file(NULL, "/does/not/exist", copy_path, 0U));
+ assert(zix_file_type(copy_path) == ZIX_FILE_TYPE_NONE);
+
+ // Fail to copy from/to a directory
+ assert(zix_copy_file(NULL, temp_dir, copy_path, 0U));
+ assert(zix_copy_file(NULL, tmp_file_path, temp_dir, 0U));
+
+ if (data_file_path) {
+ // Fail to copy a file to itself
+ assert((st = zix_copy_file(NULL, data_file_path, data_file_path, 0U)) ==
+ ZIX_STATUS_EXISTS);
+
+ // Successful copy between filesystems
+ assert(!(st = zix_copy_file(NULL, data_file_path, copy_path, 0U)));
+ assert(zix_file_equals(NULL, data_file_path, copy_path));
+
+ // Trying the same again fails because the copy path already exists
+ assert(zix_copy_file(NULL, data_file_path, copy_path, 0U) ==
+ ZIX_STATUS_EXISTS);
+
+ // Unless overwriting is requested
+ assert(!zix_copy_file(
+ NULL, data_file_path, copy_path, ZIX_COPY_OPTION_OVERWRITE_EXISTING));
+
+ assert(!zix_remove(copy_path));
+ }
+
+ // Successful copy within a filesystem
+ assert(zix_file_type(copy_path) == ZIX_FILE_TYPE_NONE);
+ assert(!(st = zix_copy_file(NULL, tmp_file_path, copy_path, 0U)));
+ assert(zix_file_equals(NULL, tmp_file_path, copy_path));
+ assert(!zix_remove(copy_path));
+
+ if (zix_file_type("/dev/random") == ZIX_FILE_TYPE_CHARACTER) {
+ // Fail to copy infinite file to a file
+ assert((st = zix_copy_file(NULL, "/dev/random", copy_path, 0U)) ==
+ ZIX_STATUS_BAD_ARG);
+
+ // Fail to copy infinite file to itself
+ assert((st = zix_copy_file(NULL, "/dev/random", "/dev/random", 0U)) ==
+ ZIX_STATUS_BAD_ARG);
+
+ // Fail to copy infinite file to another
+ assert((st = zix_copy_file(NULL, "/dev/random", "/dev/urandom", 0U)) ==
+ ZIX_STATUS_BAD_ARG);
+ }
+
+ if (zix_file_type("/dev/full") == ZIX_FILE_TYPE_CHARACTER) {
+ if (data_file_path) {
+ assert((st = zix_copy_file(NULL,
+ data_file_path,
+ "/dev/full",
+ ZIX_COPY_OPTION_OVERWRITE_EXISTING)) ==
+ ZIX_STATUS_NO_SPACE);
+ }
+
+ // Copy short file (error after flushing)
+ assert((
+ st = zix_copy_file(
+ NULL, tmp_file_path, "/dev/full", ZIX_COPY_OPTION_OVERWRITE_EXISTING)));
+
+ // Copy long file (error during writing)
+ FILE* const f = fopen(tmp_file_path, "w");
+ for (size_t i = 0; i < 4096; ++i) {
+ fprintf(f, "test\n");
+ }
+ fclose(f);
+ assert((
+ st = zix_copy_file(
+ NULL, tmp_file_path, "/dev/full", ZIX_COPY_OPTION_OVERWRITE_EXISTING)));
+ }
+
+ assert(!(st = zix_remove(tmp_file_path)));
+ assert(!(st = zix_remove(temp_dir)));
+
+ free(copy_path);
+ free(tmp_file_path);
+ free(temp_dir);
+}
+
+static void
+test_flock(void)
+{
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
+ char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
+ assert(temp_dir);
+ assert(file_path);
+
+ FILE* const f1 = fopen(file_path, "w");
+ FILE* const f2 = fopen(file_path, "w");
+
+ ZixStatus st = zix_file_lock(f1, ZIX_FILE_LOCK_TRY);
+ assert(!st || st == ZIX_STATUS_NOT_SUPPORTED);
+
+ if (!st) {
+ assert(zix_file_lock(f2, ZIX_FILE_LOCK_TRY) == ZIX_STATUS_UNAVAILABLE);
+ assert(!zix_file_unlock(f1, ZIX_FILE_LOCK_TRY));
+
+ // assert(zix_file_unlock(f1, ZIX_FILE_LOCK_TRY));
+ // assert(zix_file_unlock(f1, ZIX_FILE_LOCK_BLOCK));
+ }
+
+ // assert(zix_file_unlock(stdout, ZIX_FILE_LOCK_TRY));
+ // assert(zix_file_unlock(stdout, ZIX_FILE_LOCK_BLOCK));
+
+ fclose(f2);
+ fclose(f1);
+ assert(!zix_remove(file_path));
+ assert(!zix_remove(temp_dir));
+ free(file_path);
+ free(temp_dir);
+}
+
+typedef struct {
+ size_t n_names;
+ char** names;
+} FileList;
+
+static void
+visit(const char* const path, const char* const name, void* const data)
+{
+ (void)path;
+
+ const size_t name_len = strlen(name);
+ FileList* const file_list = (FileList*)data;
+
+ char** const new_names =
+ (char**)realloc(file_list->names, sizeof(char*) * ++file_list->n_names);
+
+ if (new_names) {
+ char* const name_copy = (char*)calloc(name_len + 1, 1);
+ memcpy(name_copy, name, name_len + 1);
+
+ file_list->names = new_names;
+ file_list->names[file_list->n_names - 1] = name_copy;
+ }
+}
+
+static void
+test_dir_for_each(void)
+{
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
+ char* const path1 = zix_path_join(NULL, temp_dir, "zix_test_1");
+ char* const path2 = zix_path_join(NULL, temp_dir, "zix_test_2");
+ assert(temp_dir);
+ assert(path1);
+ assert(path2);
+
+ FILE* const f1 = fopen(path1, "w");
+ FILE* const f2 = fopen(path2, "w");
+ fprintf(f1, "test\n");
+ fprintf(f2, "test\n");
+ fclose(f2);
+ fclose(f1);
+
+ FileList file_list = {0, NULL};
+ zix_dir_for_each(temp_dir, &file_list, visit);
+
+ assert(file_list.names);
+ assert((!strcmp(file_list.names[0], "zix_test_1") &&
+ !strcmp(file_list.names[1], "zix_test_2")) ||
+ (!strcmp(file_list.names[0], "zix_test_2") &&
+ !strcmp(file_list.names[1], "zix_test_1")));
+
+ assert(!zix_remove(path2));
+ assert(!zix_remove(path1));
+ assert(!zix_remove(temp_dir));
+
+ free(file_list.names[0]);
+ free(file_list.names[1]);
+ free(file_list.names);
+ free(path2);
+ free(path1);
+ free(temp_dir);
+}
+
+static void
+test_create_temporary_directory(void)
+{
+ assert(!zix_create_temporary_directory(NULL, ""));
+
+ char* const path1 = create_temp_dir("zixXXXXXX");
+
+ assert(path1);
+ assert(zix_file_type(path1) == ZIX_FILE_TYPE_DIRECTORY);
+
+ char* const path2 = create_temp_dir("zixXXXXXX");
+
+ assert(path2);
+ assert(strcmp(path1, path2));
+ assert(zix_file_type(path1) == ZIX_FILE_TYPE_DIRECTORY);
+ assert(zix_file_type(path2) == ZIX_FILE_TYPE_DIRECTORY);
+
+ assert(!zix_remove(path2));
+ assert(!zix_remove(path1));
+ free(path2);
+ free(path1);
+}
+
+static void
+test_create_directory_like(void)
+{
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
+ assert(temp_dir);
+ assert(zix_file_type(temp_dir) == ZIX_FILE_TYPE_DIRECTORY);
+
+ char* const sub_dir = zix_path_join(NULL, temp_dir, "sub");
+ assert(zix_create_directory_like(sub_dir, sub_dir) == ZIX_STATUS_NOT_FOUND);
+ assert(!zix_create_directory_like(sub_dir, temp_dir));
+ assert(zix_file_type(sub_dir) == ZIX_FILE_TYPE_DIRECTORY);
+ assert(!zix_remove(sub_dir));
+ zix_free(NULL, sub_dir);
+
+ assert(!zix_remove(temp_dir));
+ free(temp_dir);
+}
+
+static void
+test_create_directories(void)
+{
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
+
+ assert(temp_dir);
+ assert(zix_file_type(temp_dir) == ZIX_FILE_TYPE_DIRECTORY);
+ assert(zix_create_directories(NULL, "") == ZIX_STATUS_BAD_ARG);
+
+ char* const child_dir = zix_path_join(NULL, temp_dir, "child");
+ char* const grandchild_dir = zix_path_join(NULL, child_dir, "grandchild");
+
+ assert(!zix_create_directories(NULL, grandchild_dir));
+ assert(zix_file_type(grandchild_dir) == ZIX_FILE_TYPE_DIRECTORY);
+ assert(zix_file_type(child_dir) == ZIX_FILE_TYPE_DIRECTORY);
+
+ char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
+ FILE* const f = fopen(file_path, "w");
+
+ fprintf(f, "test\n");
+ fclose(f);
+
+ assert(zix_create_directories(NULL, file_path) == ZIX_STATUS_EXISTS);
+
+ assert(!zix_remove(file_path));
+ assert(!zix_remove(grandchild_dir));
+ assert(!zix_remove(child_dir));
+ assert(!zix_remove(temp_dir));
+ free(file_path);
+ free(child_dir);
+ free(grandchild_dir);
+ free(temp_dir);
+}
+
+static void
+test_file_equals(void)
+{
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
+ char* const path1 = zix_path_join(NULL, temp_dir, "zix1");
+ char* const path2 = zix_path_join(NULL, temp_dir, "zix2");
+ assert(temp_dir);
+
+ // Equal: test, test
+ assert(!write_to_path(path1, "test"));
+ assert(!write_to_path(path2, "test"));
+ assert(zix_file_equals(NULL, path1, path2));
+
+ // Missing files
+ assert(!zix_file_equals(NULL, path1, "/does/not/exist"));
+ assert(!zix_file_equals(NULL, "/does/not/exist", path2));
+
+ // Longer RHS: test, testdiff
+ assert(!write_to_path(path2, "diff"));
+ assert(!zix_file_equals(NULL, path1, path2));
+
+ // Longer LHS: testdifflong, testdiff
+ assert(!write_to_path(path1, "difflong"));
+ assert(!zix_file_equals(NULL, path1, path2));
+
+ // Equal sizes but different content: testdifflong, testdifflang
+ assert(!write_to_path(path2, "difflang"));
+ assert(!zix_file_equals(NULL, path1, path2));
+
+ assert(zix_file_equals(NULL, path1, path1));
+ assert(zix_file_equals(NULL, path2, path2));
+
+ assert(!zix_remove(path2));
+ assert(!zix_remove(path1));
+ assert(!zix_remove(temp_dir));
+
+ free(path2);
+ free(path1);
+ free(temp_dir);
+}
+
+static void
+test_file_size(void)
+{
+ static const char* const contents = "file size test";
+
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
+ char* const path = zix_path_join(NULL, temp_dir, "zix_test");
+
+ assert(temp_dir);
+ assert(!write_to_path(path, contents));
+
+ const ZixFileOffset size = zix_file_size(path);
+ assert(size > 0);
+ assert((size_t)size == strlen(contents));
+
+ assert(!zix_remove(path));
+ assert(!zix_remove(temp_dir));
+
+ free(path);
+ free(temp_dir);
+}
+
+static void
+test_create_symlink(void)
+{
+ static const char* const contents = "zixtest";
+
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
+ assert(temp_dir);
+
+ // Write contents to original file
+ char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
+ {
+ FILE* const f = fopen(file_path, "w");
+ fprintf(f, "%s", contents);
+ fclose(f);
+ }
+
+ // Ensure original file exists and is a regular file
+ assert(zix_symlink_type(file_path) == ZIX_FILE_TYPE_REGULAR);
+
+ // Create symlink to original file
+ char* const link_path = zix_path_join(NULL, temp_dir, "zix_test_link");
+ const ZixStatus st = zix_create_symlink(file_path, link_path);
+
+ // Check that the symlink seems equivalent to the original file
+ assert(!st || st == ZIX_STATUS_NOT_SUPPORTED || st == ZIX_STATUS_BAD_PERMS);
+ if (!st) {
+ assert(zix_symlink_type(link_path) == ZIX_FILE_TYPE_SYMLINK);
+ assert(zix_file_type(link_path) == ZIX_FILE_TYPE_REGULAR);
+ assert(zix_file_equals(NULL, file_path, link_path));
+ assert(!zix_remove(link_path));
+ } else {
+ assert(zix_symlink_type(link_path) == ZIX_FILE_TYPE_NONE);
+ }
+
+ assert(!zix_remove(file_path));
+ assert(!zix_remove(temp_dir));
+
+ free(link_path);
+ free(file_path);
+ free(temp_dir);
+}
+
+static void
+test_create_directory_symlink(void)
+{
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
+ assert(temp_dir);
+
+ char* const link_path = zix_path_join(NULL, temp_dir, "zix_test_link");
+ const ZixStatus st = zix_create_directory_symlink(temp_dir, link_path);
+
+ if (st != ZIX_STATUS_NOT_SUPPORTED && st != ZIX_STATUS_BAD_PERMS) {
+ assert(!st);
+ assert(zix_file_type(link_path) == ZIX_FILE_TYPE_DIRECTORY);
+
+#ifdef _WIN32
+ assert(zix_symlink_type(link_path) == ZIX_FILE_TYPE_DIRECTORY);
+#else
+ assert(zix_symlink_type(link_path) == ZIX_FILE_TYPE_SYMLINK);
+#endif
+
+ assert(!zix_remove(link_path));
+ }
+
+ assert(!zix_remove(temp_dir));
+ free(link_path);
+ free(temp_dir);
+}
+
+static void
+test_create_hard_link(void)
+{
+ static const char* const contents = "zixtest";
+
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
+ assert(temp_dir);
+
+ // Write contents to original file
+ char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
+ {
+ FILE* const f = fopen(file_path, "w");
+ fprintf(f, "%s", contents);
+ fclose(f);
+ }
+
+ // Ensure original file exists and is a regular file
+ assert(zix_symlink_type(file_path) == ZIX_FILE_TYPE_REGULAR);
+
+ // Create symlink to original file
+ char* const link_path = zix_path_join(NULL, temp_dir, "zix_test_link");
+ const ZixStatus st = zix_create_hard_link(file_path, link_path);
+
+ // Check that the link is equivalent to the original file
+ assert(!st || st == ZIX_STATUS_NOT_SUPPORTED || st == ZIX_STATUS_MAX_LINKS);
+ if (!st) {
+ assert(zix_file_type(link_path) == ZIX_FILE_TYPE_REGULAR);
+ assert(zix_file_equals(NULL, file_path, link_path));
+ assert(!zix_remove(link_path));
+ } else {
+ assert(zix_symlink_type(link_path) == ZIX_FILE_TYPE_NONE);
+ }
+
+ assert(!zix_remove(file_path));
+ assert(!zix_remove(temp_dir));
+
+ free(link_path);
+ free(file_path);
+ free(temp_dir);
+}
+
+int
+main(const int argc, char** const argv)
+{
+#ifdef __EMSCRIPTEN__
+ const char* const data_file_path = NULL;
+#else
+ const char* const data_file_path = argc > 1 ? argv[1] : "build.ninja";
+#endif
+
+ test_temp_directory_path();
+ test_current_path();
+ test_canonical_path();
+ test_file_type();
+ test_path_exists();
+ test_is_directory();
+ test_copy_file(data_file_path);
+ test_flock();
+ test_dir_for_each();
+ test_create_temporary_directory();
+ test_create_directory_like();
+ test_create_directories();
+ test_file_equals();
+ test_file_size();
+ test_create_symlink();
+ test_create_directory_symlink();
+ test_create_hard_link();
+
+ return 0;
+}
diff --git a/test/test_status.c b/test/test_status.c
index ca47d6e..6857928 100644
--- a/test/test_status.c
+++ b/test/test_status.c
@@ -1,4 +1,4 @@
-// Copyright 2021 David Robillard <d@drobilla.net>
+// Copyright 2021-2022 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#undef NDEBUG
@@ -15,7 +15,7 @@ test_strerror(void)
const char* msg = zix_strerror(ZIX_STATUS_SUCCESS);
assert(!strcmp(msg, "Success"));
- for (int i = ZIX_STATUS_ERROR; i <= ZIX_STATUS_UNAVAILABLE; ++i) {
+ for (int i = ZIX_STATUS_ERROR; i <= ZIX_STATUS_MAX_LINKS; ++i) {
msg = zix_strerror((ZixStatus)i);
assert(strcmp(msg, "Success"));
}