#include "test_shared_utils.h" #include "utils.h" #include "protocol.h" #include "test_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include /* ---- delete-walker tests ---- */ static char* make_walk_root(const char* tag) { char* path = malloc(256); if (!path) return NULL; snprintf(path, 256, "/tmp/fastsync_walk_%s_%d", tag, (int)getpid()); rmdir(path); if (mkdir(path, 0755) != 0) { free(path); return NULL; } return path; } static bool write_file_at(const char* dir, const char* name, const char* content) { char* path = path_cat(dir, name); if (!path) return false; int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); bool ok = fd >= 0; if (fd >= 0) { if (content) { const char* p = content; size_t remaining = strlen(content); while (remaining > 0) { ssize_t n = write(fd, p, remaining); if (n <= 0) { ok = false; break; } p += n; remaining -= (size_t)n; } } close(fd); } free(path); return ok; } static bool file_exists(const char* dir, const char* name) { char* path = path_cat(dir, name); bool exists = path && access(path, F_OK) == 0; free(path); return exists; } static bool dir_exists(const char* dir, const char* name) { char* path = path_cat(dir, name); struct stat st; bool exists = path && stat(path, &st) == 0 && S_ISDIR(st.st_mode); free(path); return exists; } static int make_subdir(const char* root, const char* name) { char* path = path_cat(root, name); int rc = -1; if (path) { rc = mkdir(path, 0755); free(path); } return rc; } static void remove_walk_tree(const char* path) { DIR* dir = opendir(path); if (!dir) { rmdir(path); return; } const struct dirent* entry; while ((entry = readdir(dir)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; char* child = path_cat(path, entry->d_name); if (child) { struct stat st; if (lstat(child, &st) == 0 && S_ISDIR(st.st_mode)) remove_walk_tree(child); else unlink(child); free(child); } } closedir(dir); rmdir(path); } static ArrayList* make_manifest_strings(const char* const* entries, int count) { ArrayList* manifest = array_list_create(free); if (!manifest) return NULL; for (int i = 0; i < count; i++) { char* dup = str_dup(entries[i]); if (!dup || !array_list_add(manifest, dup)) { free(dup); array_list_delete(manifest); return NULL; } } return manifest; } static void test_walker_removes_extras_keeps_manifest_and_protected() { char* root = make_walk_root("basic"); EXPECT_NOT_NULL(root); EXPECT_TRUE(write_file_at(root, "a.txt", "extra")); EXPECT_TRUE(write_file_at(root, "keep.txt", "kept")); EXPECT_EQ_INT(make_subdir(root, "d"), 0); EXPECT_TRUE(write_file_at(root, "d/e.txt", "extra")); EXPECT_TRUE(write_file_at(root, "d/k.txt", "kept")); EXPECT_EQ_INT(make_subdir(root, "prot"), 0); EXPECT_TRUE(write_file_at(root, "prot/f.txt", "untouched")); const char* keeps[] = {"keep.txt", "d/k.txt"}; ArrayList* manifest = make_manifest_strings(keeps, 2); EXPECT_NOT_NULL(manifest); DeleteSkipEntry skip = {"prot", false}; size_t deleted = 0; DeleteWalkResult result = delete_extras_limited(root, manifest, 100000, &skip, 1, &deleted); EXPECT_EQ_INT((int)result, (int)DELETE_WALK_OK); EXPECT_FALSE(file_exists(root, "a.txt")); EXPECT_TRUE(file_exists(root, "keep.txt")); EXPECT_FALSE(file_exists(root, "d/e.txt")); EXPECT_TRUE(file_exists(root, "d/k.txt")); EXPECT_TRUE(dir_exists(root, "d")); EXPECT_TRUE(file_exists(root, "prot/f.txt")); EXPECT_TRUE(deleted >= 2); array_list_delete(manifest); remove_walk_tree(root); free(root); } static void test_walker_keeps_nested_manifest_dirs() { /* The keep-set index must preserve deep content: a directory is protected when its own name is a keep entry OR when kept content lives below it, and an exact kept file survives while its siblings are removed. */ char* root = make_walk_root("nestedkeep"); EXPECT_NOT_NULL(root); EXPECT_TRUE(write_file_at(root, "extra.txt", "extra")); EXPECT_EQ_INT(make_subdir(root, "keepdir"), 0); EXPECT_EQ_INT(make_subdir(root, "keepdir/deep"), 0); EXPECT_TRUE(write_file_at(root, "keepdir/deep/keep.txt", "kept")); EXPECT_TRUE(write_file_at(root, "keepdir/extra2.txt", "extra")); EXPECT_EQ_INT(make_subdir(root, "dropdir"), 0); EXPECT_EQ_INT(make_subdir(root, "keep2"), 0); EXPECT_TRUE(write_file_at(root, "keep2/inner.txt", "kept")); EXPECT_EQ_INT(make_subdir(root, "keep3"), 0); const char* keeps[] = {"keepdir/deep/keep.txt", "keep2/inner.txt", "keep3"}; ArrayList* manifest = make_manifest_strings(keeps, 3); EXPECT_NOT_NULL(manifest); size_t deleted = 0; DeleteWalkResult result = delete_extras_limited(root, manifest, 100000, NULL, 0, &deleted); EXPECT_EQ_INT((int)result, (int)DELETE_WALK_OK); EXPECT_FALSE(file_exists(root, "extra.txt")); EXPECT_TRUE(file_exists(root, "keepdir/deep/keep.txt")); EXPECT_FALSE(file_exists(root, "keepdir/extra2.txt")); EXPECT_TRUE(dir_exists(root, "keepdir")); EXPECT_TRUE(dir_exists(root, "keepdir/deep")); EXPECT_FALSE(dir_exists(root, "dropdir")); EXPECT_TRUE(dir_exists(root, "keep2")); EXPECT_TRUE(file_exists(root, "keep2/inner.txt")); EXPECT_TRUE(dir_exists(root, "keep3")); /* an exact directory keep entry survives */ EXPECT_EQ_INT((int)deleted, 3); array_list_delete(manifest); remove_walk_tree(root); free(root); } static void test_walker_max_delete_exceeded_deletes_nothing() { char* root = make_walk_root("maxdel"); EXPECT_NOT_NULL(root); EXPECT_TRUE(write_file_at(root, "a.txt", "extra")); EXPECT_TRUE(write_file_at(root, "b.txt", "extra")); EXPECT_TRUE(write_file_at(root, "c.txt", "extra")); const char* keeps[1] = {NULL}; ArrayList* manifest = make_manifest_strings(keeps, 0); EXPECT_NOT_NULL(manifest); size_t deleted = 999; DeleteWalkResult result = delete_extras_limited(root, manifest, 2, NULL, 0, &deleted); EXPECT_EQ_INT((int)result, (int)DELETE_WALK_LIMIT_EXCEEDED); EXPECT_EQ_INT((int)deleted, 0); EXPECT_TRUE(file_exists(root, "a.txt")); EXPECT_TRUE(file_exists(root, "b.txt")); EXPECT_TRUE(file_exists(root, "c.txt")); array_list_delete(manifest); remove_walk_tree(root); free(root); } static void test_walker_max_delete_exact_bound_deletes() { char* root = make_walk_root("maxdel2"); EXPECT_NOT_NULL(root); EXPECT_TRUE(write_file_at(root, "a.txt", "extra")); EXPECT_TRUE(write_file_at(root, "b.txt", "extra")); const char* keeps[1] = {NULL}; ArrayList* manifest = make_manifest_strings(keeps, 0); EXPECT_NOT_NULL(manifest); size_t deleted = 0; DeleteWalkResult result = delete_extras_limited(root, manifest, 2, NULL, 0, &deleted); EXPECT_EQ_INT((int)result, (int)DELETE_WALK_OK); EXPECT_EQ_INT((int)deleted, 2); EXPECT_FALSE(file_exists(root, "a.txt")); EXPECT_FALSE(file_exists(root, "b.txt")); array_list_delete(manifest); remove_walk_tree(root); free(root); } static void test_walker_unlimited_deletes_all() { char* root = make_walk_root("unlim"); EXPECT_NOT_NULL(root); EXPECT_TRUE(write_file_at(root, "a.txt", "extra")); EXPECT_TRUE(write_file_at(root, "b.txt", "extra")); EXPECT_EQ_INT(make_subdir(root, "emptydir"), 0); const char* keeps[1] = {NULL}; ArrayList* manifest = make_manifest_strings(keeps, 0); EXPECT_NOT_NULL(manifest); EXPECT_TRUE(delete_extras(root, manifest)); EXPECT_FALSE(file_exists(root, "a.txt")); EXPECT_FALSE(file_exists(root, "b.txt")); EXPECT_FALSE(dir_exists(root, "emptydir")); array_list_delete(manifest); remove_walk_tree(root); free(root); } /* The 100000-entry server hard bound (MAX_SERVER_DELETE_COUNT, which this test exercises through a literal to avoid reaching into file_receive.c) is also all-or-nothing: a destination holding more extras than the bound must be left completely untouched. Skipped under valgrind: 100k file creations would be far too slow under instrumentation. */ static void test_walker_hard_bound_all_or_nothing() { if (is_running_under_valgrind()) return; enum { HARD_BOUND = 100000 }; char* root = make_walk_root("hardbound"); EXPECT_NOT_NULL(root); int rootfd = open(root, O_RDONLY | O_DIRECTORY | O_CLOEXEC); EXPECT_TRUE(rootfd >= 0); bool created = true; for (int i = 0; created && i < HARD_BOUND + 1; i++) { char name[32]; snprintf(name, sizeof(name), "f%d", i); int fd = openat(rootfd, name, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) created = false; else close(fd); } EXPECT_TRUE(created); const char* keeps[1] = {NULL}; ArrayList* manifest = make_manifest_strings(keeps, 0); EXPECT_NOT_NULL(manifest); size_t deleted = 999; DeleteWalkResult result = delete_extras_limited(root, manifest, HARD_BOUND, NULL, 0, &deleted); EXPECT_EQ_INT((int)result, (int)DELETE_WALK_LIMIT_EXCEEDED); EXPECT_EQ_INT((int)deleted, 0); EXPECT_TRUE(file_exists(root, "f0")); EXPECT_TRUE(file_exists(root, "f100000")); array_list_delete(manifest); /* Fast cleanup: unlink every created name through the still-open root fd. */ if (rootfd >= 0) { for (int i = 0; i < HARD_BOUND + 1; i++) { char name[32]; snprintf(name, sizeof(name), "f%d", i); (void)unlinkat(rootfd, name, 0); } close(rootfd); } rmdir(root); free(root); } typedef struct { bool eight_bit_output; const char* expected; int failed; } EscapeThreadArgs; static int escape_thread(void* arg) { EscapeThreadArgs* args = arg; for (int i = 0; i < 1000; i++) { char* escaped = output_escape("x\xc3\xa9\n", args->eight_bit_output); if (!escaped || strcmp(escaped, args->expected) != 0) args->failed = 1; free(escaped); } return 0; } /* A7-3/S1 transport classification: the daemon auth gate and the client credential rule both key off these helpers, so cover the exact accepted forms plus the negative cases. */ static void test_loopback_helpers() { /* Host strings. */ EXPECT_TRUE(utils_host_is_loopback("localhost")); EXPECT_TRUE(utils_host_is_loopback("127.0.0.1")); EXPECT_TRUE(utils_host_is_loopback("127.255.255.254")); EXPECT_TRUE(utils_host_is_loopback("127.0.0.0")); EXPECT_TRUE(utils_host_is_loopback("::1")); EXPECT_TRUE(utils_host_is_loopback("[::1]")); EXPECT_FALSE(utils_host_is_loopback("128.0.0.1")); EXPECT_FALSE(utils_host_is_loopback("10.0.0.1")); EXPECT_FALSE(utils_host_is_loopback("0.0.0.0")); EXPECT_FALSE(utils_host_is_loopback("example.com")); EXPECT_FALSE(utils_host_is_loopback("")); EXPECT_FALSE(utils_host_is_loopback(NULL)); /* Raw sockaddr classification. */ struct sockaddr_in v4; memset(&v4, 0, sizeof(v4)); v4.sin_family = AF_INET; EXPECT_TRUE(inet_pton(AF_INET, "127.0.0.1", &v4.sin_addr) == 1); EXPECT_TRUE(utils_sockaddr_is_loopback((const struct sockaddr*)&v4)); EXPECT_TRUE(inet_pton(AF_INET, "127.5.5.5", &v4.sin_addr) == 1); EXPECT_TRUE(utils_sockaddr_is_loopback((const struct sockaddr*)&v4)); EXPECT_TRUE(inet_pton(AF_INET, "128.0.0.1", &v4.sin_addr) == 1); EXPECT_FALSE(utils_sockaddr_is_loopback((const struct sockaddr*)&v4)); struct sockaddr_in6 v6; memset(&v6, 0, sizeof(v6)); v6.sin6_family = AF_INET6; EXPECT_TRUE(inet_pton(AF_INET6, "::1", &v6.sin6_addr) == 1); EXPECT_TRUE(utils_sockaddr_is_loopback((const struct sockaddr*)&v6)); EXPECT_TRUE(inet_pton(AF_INET6, "::ffff:127.0.0.1", &v6.sin6_addr) == 1); EXPECT_TRUE(utils_sockaddr_is_loopback((const struct sockaddr*)&v6)); EXPECT_TRUE(inet_pton(AF_INET6, "::ffff:127.255.255.254", &v6.sin6_addr) == 1); EXPECT_TRUE(utils_sockaddr_is_loopback((const struct sockaddr*)&v6)); EXPECT_TRUE(inet_pton(AF_INET6, "::ffff:10.0.0.1", &v6.sin6_addr) == 1); EXPECT_FALSE(utils_sockaddr_is_loopback((const struct sockaddr*)&v6)); EXPECT_FALSE(utils_sockaddr_is_loopback(NULL)); /* A pipe has no socket peer: getpeername fails with ENOTSOCK. The helper is fail-closed, so an unprovable channel is NOT local (daemon auth modules are daemon-only and never run over the --stdio pipe). */ int pipe_fds[2]; EXPECT_EQ_INT(pipe(pipe_fds), 0); EXPECT_FALSE(utils_fd_peer_is_local(pipe_fds[0])); close(pipe_fds[0]); close(pipe_fds[1]); EXPECT_FALSE(utils_fd_peer_is_local(-1)); /* A connected AF_UNIX socketpair is a socket, but its peer is not a loopback IP address, so it is not local either. */ int pair_fds[2]; EXPECT_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, pair_fds), 0); EXPECT_FALSE(utils_fd_peer_is_local(pair_fds[0])); close(pair_fds[0]); close(pair_fds[1]); /* A real loopback TCP peer is local. */ int listener = socket(AF_INET, SOCK_STREAM, 0); EXPECT_TRUE(listener >= 0); struct sockaddr_in bind_addr; memset(&bind_addr, 0, sizeof(bind_addr)); bind_addr.sin_family = AF_INET; bind_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); bind_addr.sin_port = 0; EXPECT_EQ_INT(bind(listener, (const struct sockaddr*)&bind_addr, sizeof(bind_addr)), 0); EXPECT_EQ_INT(listen(listener, 1), 0); socklen_t addr_len = sizeof(bind_addr); EXPECT_EQ_INT(getsockname(listener, (struct sockaddr*)&bind_addr, &addr_len), 0); int dialer = socket(AF_INET, SOCK_STREAM, 0); EXPECT_TRUE(dialer >= 0); EXPECT_EQ_INT(connect(dialer, (const struct sockaddr*)&bind_addr, sizeof(bind_addr)), 0); int accepted = accept(listener, NULL, NULL); EXPECT_TRUE(accepted >= 0); EXPECT_TRUE(utils_fd_peer_is_local(accepted)); close(accepted); close(dialer); close(listener); } /* The daemon host ACL reads the numeric peer address through * utils_fd_peer_ip. A real loopback TCP peer reports "127.0.0.1"; a pipe or an * AF_UNIX socketpair has no INET peer and must return false with an empty * buffer (the fail-closed "cannot tell" result). */ static void test_fd_peer_ip() { char ip[INET6_ADDRSTRLEN]; EXPECT_FALSE(utils_fd_peer_ip(-1, ip, sizeof(ip))); EXPECT_EQ_STR(ip, ""); EXPECT_FALSE(utils_fd_peer_ip(-1, NULL, 0)); int pipe_fds[2]; EXPECT_EQ_INT(pipe(pipe_fds), 0); EXPECT_FALSE(utils_fd_peer_ip(pipe_fds[0], ip, sizeof(ip))); EXPECT_EQ_STR(ip, ""); close(pipe_fds[0]); close(pipe_fds[1]); int pair_fds[2]; EXPECT_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, pair_fds), 0); EXPECT_FALSE(utils_fd_peer_ip(pair_fds[0], ip, sizeof(ip))); EXPECT_EQ_STR(ip, ""); close(pair_fds[0]); close(pair_fds[1]); int listener = socket(AF_INET, SOCK_STREAM, 0); EXPECT_TRUE(listener >= 0); struct sockaddr_in bind_addr; memset(&bind_addr, 0, sizeof(bind_addr)); bind_addr.sin_family = AF_INET; bind_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); bind_addr.sin_port = 0; EXPECT_EQ_INT(bind(listener, (const struct sockaddr*)&bind_addr, sizeof(bind_addr)), 0); EXPECT_EQ_INT(listen(listener, 1), 0); socklen_t addr_len = sizeof(bind_addr); EXPECT_EQ_INT(getsockname(listener, (struct sockaddr*)&bind_addr, &addr_len), 0); int dialer = socket(AF_INET, SOCK_STREAM, 0); EXPECT_TRUE(dialer >= 0); EXPECT_EQ_INT(connect(dialer, (const struct sockaddr*)&bind_addr, sizeof(bind_addr)), 0); int accepted = accept(listener, NULL, NULL); EXPECT_TRUE(accepted >= 0); EXPECT_TRUE(utils_fd_peer_ip(accepted, ip, sizeof(ip))); EXPECT_EQ_STR(ip, "127.0.0.1"); /* utils_sockaddr_to_string includes the port for a real peer. */ struct sockaddr_storage peer; socklen_t peer_len = sizeof(peer); EXPECT_EQ_INT(getpeername(accepted, (struct sockaddr*)&peer, &peer_len), 0); char peer_string[128]; EXPECT_TRUE( utils_sockaddr_to_string((const struct sockaddr*)&peer, peer_string, sizeof(peer_string))); EXPECT_TRUE(strncmp(peer_string, "127.0.0.1:", strlen("127.0.0.1:")) == 0); close(accepted); close(dialer); close(listener); /* A non-INET family formats to "unknown" at the call site, not a bogus IP. */ struct sockaddr sa_unix; memset(&sa_unix, 0, sizeof(sa_unix)); sa_unix.sa_family = AF_UNIX; EXPECT_FALSE(utils_sockaddr_to_string(&sa_unix, peer_string, sizeof(peer_string))); EXPECT_EQ_STR(peer_string, ""); } /* The keep/files-from indexes must store exactly the input entries (one node each), never a copied ancestor prefix per component. This builds a PathIndex over paths thousands of components deep and checks the structural bound plus the exact / descendant query semantics. */ static void test_path_index_bounded() { enum { COUNT = 8, COMPONENTS = 5000 }; size_t entry_len = (size_t)COMPONENTS * 2 + 2; /* trailing "xN" */ char* storage = malloc((size_t)COUNT * (entry_len + 1)); EXPECT_NOT_NULL(storage); const char** entries = calloc(COUNT, sizeof(char*)); EXPECT_NOT_NULL(entries); for (int i = 0; i < COUNT; i++) { char* entry = storage + (size_t)i * (entry_len + 1); size_t pos = 0; for (int c = 0; c < COMPONENTS; c++) { entry[pos++] = 'a'; entry[pos++] = '/'; } entry[pos++] = 'x'; entry[pos++] = (char)('0' + i); entry[pos] = '\0'; entries[i] = entry; } PathIndex index; EXPECT_TRUE(path_index_build(&index, entries, COUNT)); EXPECT_EQ_INT((int)index.sorted.count, COUNT); EXPECT_EQ_INT((int)index.exact.size, COUNT); EXPECT_TRUE(path_index_contains(&index, entries[0])); EXPECT_FALSE(path_index_contains(&index, "a")); EXPECT_TRUE(path_index_has_descendant(&index, "a")); EXPECT_TRUE(path_index_has_descendant(&index, "a/a")); EXPECT_FALSE(path_index_has_descendant(&index, "aa")); path_index_free(&index); free((void*)entries); free(storage); } static void test_path_index_semantics() { const char* entries[] = {"a/b/c.txt", "a/b/d.txt", "x.txt", "deep/deeper/deepest"}; PathIndex index; EXPECT_TRUE(path_index_build(&index, entries, 4)); EXPECT_TRUE(path_index_contains(&index, "a/b/c.txt")); EXPECT_FALSE(path_index_contains(&index, "a/b")); EXPECT_TRUE(path_index_contains_n(&index, "a/b/c.txt/ignored", 9)); EXPECT_FALSE(path_index_contains_n(&index, "a/b/c.txt/ignored", 10)); EXPECT_TRUE(path_index_has_descendant(&index, "a")); EXPECT_TRUE(path_index_has_descendant(&index, "a/b")); EXPECT_FALSE(path_index_has_descendant(&index, "a/b/c.txt")); EXPECT_FALSE(path_index_has_descendant(&index, "ab")); EXPECT_FALSE(path_index_has_descendant(&index, "")); path_index_free(&index); /* A zero-entry index answers no queries. */ PathIndex empty; EXPECT_TRUE(path_index_build(&empty, NULL, 0)); EXPECT_EQ_INT((int)empty.sorted.count, 0); EXPECT_FALSE(path_index_contains(&empty, "a")); EXPECT_FALSE(path_index_has_descendant(&empty, "a")); path_index_free(&empty); } /* utils_getdelim_bounded must return normal short lines unchanged and refuse an * over-long record with EFBIG rather than allocating without bound. */ static void test_getdelim_bounded() { FILE* fp = tmpfile(); EXPECT_NOT_NULL(fp); const char* short_line = "short\n"; EXPECT_EQ_INT((int)fwrite(short_line, 1, strlen(short_line), fp), (int)strlen(short_line)); char big[32]; memset(big, 'x', 20); big[20] = '\n'; EXPECT_EQ_INT((int)fwrite(big, 1, 21, fp), 21); rewind(fp); char* line = NULL; size_t cap = 0; ssize_t n = utils_getdelim_bounded(fp, &line, &cap, '\n', 64); EXPECT_EQ_INT((int)n, 6); EXPECT_EQ_STR(line, "short\n"); errno = 0; n = utils_getdelim_bounded(fp, &line, &cap, '\n', 10); EXPECT_EQ_INT((int)n, -1); EXPECT_EQ_INT(errno, EFBIG); free(line); fclose(fp); } void test_shared_utils() { test_path_index_bounded(); test_path_index_semantics(); test_getdelim_bounded(); test_walker_removes_extras_keeps_manifest_and_protected(); test_walker_keeps_nested_manifest_dirs(); test_walker_max_delete_exceeded_deletes_nothing(); test_walker_max_delete_exact_bound_deletes(); test_walker_unlimited_deletes_all(); test_walker_hard_bound_all_or_nothing(); test_loopback_helpers(); test_fd_peer_ip(); /* --append / --append-verify tail-resume math: a resume is eligible only for a shorter existing destination, and the tail length is then the difference. */ EXPECT_TRUE(append_resume_eligible(0, 10)); EXPECT_TRUE(append_resume_eligible(7, 10)); EXPECT_FALSE(append_resume_eligible(10, 10)); EXPECT_FALSE(append_resume_eligible(11, 10)); unsigned long long tail; EXPECT_TRUE(append_tail_length(0, 10, &tail)); EXPECT_EQ_INT((int)tail, 10); EXPECT_TRUE(append_tail_length(7, 10, &tail)); EXPECT_EQ_INT((int)tail, 3); EXPECT_FALSE(append_tail_length(10, 10, &tail)); EXPECT_FALSE(append_tail_length(11, 10, &tail)); EXPECT_FALSE(append_tail_length(7, 10, NULL)); char formatted[32]; EXPECT_TRUE(format_human_bytes(0, formatted, sizeof(formatted))); EXPECT_EQ_STR(formatted, "0 B"); EXPECT_TRUE(format_human_bytes(1024, formatted, sizeof(formatted))); EXPECT_EQ_STR(formatted, "1.0 KB"); EXPECT_TRUE(format_human_bytes(1536 * 1024, formatted, sizeof(formatted))); EXPECT_EQ_STR(formatted, "1.5 MB"); EXPECT_FALSE(format_human_bytes(1024, formatted, 4)); char high_bit[] = {'a', (char)0xc3, (char)0xa9, '\n', '\0'}; char* escaped = output_escape(high_bit, false); EXPECT_EQ_STR(escaped, "a\\#303\\#251\\#012"); free(escaped); escaped = output_escape(high_bit, true); EXPECT_EQ_STR(escaped, "a\xc3\xa9\\#012"); free(escaped); ProtocolSession safe_session; ProtocolSession eight_bit_session; protocol_session_init(&safe_session, -1, -1); protocol_session_init(&eight_bit_session, -1, -1); protocol_session_set_8_bit_output(&safe_session, false); protocol_session_set_8_bit_output(&eight_bit_session, true); EXPECT_FALSE(safe_session.eight_bit_output); EXPECT_TRUE(eight_bit_session.eight_bit_output); EscapeThreadArgs safe_args = {false, "x\\#303\\#251\\#012", 0}; EscapeThreadArgs eight_bit_args = {true, "x\xc3\xa9\\#012", 0}; thrd_t safe_thread; thrd_t eight_bit_thread; EXPECT_EQ_INT(thrd_create(&safe_thread, escape_thread, &safe_args), thrd_success); EXPECT_EQ_INT(thrd_create(&eight_bit_thread, escape_thread, &eight_bit_args), thrd_success); EXPECT_EQ_INT(thrd_join(safe_thread, NULL), thrd_success); EXPECT_EQ_INT(thrd_join(eight_bit_thread, NULL), thrd_success); EXPECT_FALSE(safe_args.failed); EXPECT_FALSE(eight_bit_args.failed); // Test str_dup const char* dup_null = str_dup(NULL); EXPECT_NULL(dup_null); char* dup_empty = str_dup(""); EXPECT_NOT_NULL(dup_empty); EXPECT_EQ_STR(dup_empty, ""); free(dup_empty); char* dup_normal = str_dup("hello world"); EXPECT_NOT_NULL(dup_normal); EXPECT_EQ_STR(dup_normal, "hello world"); free(dup_normal); // Test path_cat char* cat1 = path_cat("/foo", "/bar"); EXPECT_NOT_NULL(cat1); EXPECT_EQ_STR(cat1, "/foo/bar"); free(cat1); char* cat2 = path_cat("/foo/", "/bar"); EXPECT_NOT_NULL(cat2); EXPECT_EQ_STR(cat2, "/foo/bar"); free(cat2); char* cat3 = path_cat("/foo", "bar"); EXPECT_NOT_NULL(cat3); EXPECT_EQ_STR(cat3, "/foo/bar"); free(cat3); char* cat4 = path_cat("/foo/", "bar"); EXPECT_NOT_NULL(cat4); EXPECT_EQ_STR(cat4, "/foo/bar"); free(cat4); char* cat_empty1 = path_cat("", "/bar"); EXPECT_NOT_NULL(cat_empty1); EXPECT_EQ_STR(cat_empty1, "/bar"); free(cat_empty1); char* cat_empty2 = path_cat("/foo", ""); EXPECT_NOT_NULL(cat_empty2); EXPECT_EQ_STR(cat_empty2, "/foo"); free(cat_empty2); char* cat_null1 = path_cat(NULL, "/bar"); EXPECT_NOT_NULL(cat_null1); EXPECT_EQ_STR(cat_null1, "/bar"); free(cat_null1); char* cat_null2 = path_cat("/foo", NULL); EXPECT_NOT_NULL(cat_null2); EXPECT_EQ_STR(cat_null2, "/foo"); free(cat_null2); }