/* Portable helpers for the test programs: scratch dirs, env, recursive * delete, symlinks and (with CST_TEST_SOCKETS) a loopback socket layer. */ #ifndef CST_TEST_UTIL_H #define CST_TEST_UTIL_H #include "os_compat.h" #include #include #include #include #ifdef _WIN32 #ifdef CST_TEST_SOCKETS #include /* before windows.h */ #include #endif #include #include #include #else #include #include #include #endif #if defined(__GNUC__) #define CST_TEST_UNUSED __attribute__((unused)) #else #define CST_TEST_UNUSED #endif /* mkdir(path, mode) that also builds on Windows. */ #define test_mkdir(p, mode) os_mkdir((p), (mode)) /* Fresh empty directory "/--"; 0 on success. */ static CST_TEST_UNUSED int test_tmpdir(char *out, size_t n, const char *tag) { static int seq; char base[1024]; if (os_temp_dir(base, sizeof(base)) != 0) snprintf(base, sizeof(base), "."); for (int i = 0; i < 100; i++) { int k = snprintf(out, n, "%s/%s-%d-%d", base, tag, os_getpid(), seq++); if (k < 0 || (size_t)k >= n) return -1; #ifdef _WIN32 int rc = _mkdir(out); #else int rc = mkdir(out, 0755); #endif if (rc == 0) return 0; if (errno != EEXIST) return -1; } return -1; } /* rm -rf without a shell; links are removed, never followed. */ static CST_TEST_UNUSED int test_rmrf(const char *path) { os_stat_t st; if (os_lstat(path, &st) != 0) return 0; if (st.is_link) { #ifdef _WIN32 if (RemoveDirectoryA(path) || DeleteFileA(path)) return 0; return -1; #else return unlink(path); #endif } if (!st.is_dir) { #ifdef _WIN32 SetFileAttributesA(path, FILE_ATTRIBUTE_NORMAL); #endif return remove(path); } os_dir_t *d = os_dir_open(path); if (d) { const os_dirent_t *e; while ((e = os_dir_next(d))) { if (!strcmp(e->name, ".") || !strcmp(e->name, "..")) continue; size_t L = strlen(path) + strlen(e->name) + 2; char *c = malloc(L); if (!c) break; snprintf(c, L, "%s/%s", path, e->name); test_rmrf(c); free(c); } os_dir_close(d); } #ifdef _WIN32 return _rmdir(path); #else return rmdir(path); #endif } /* 0 on success; -1 when the platform or account cannot make links (Windows * without Developer Mode or admin rights): callers skip those checks. */ static CST_TEST_UNUSED int test_symlink(const char *target, const char *link) { #ifdef _WIN32 wchar_t *wt = os_utf8_to_wide(target), *wl = os_utf8_to_wide(link); int ok = 0; if (wt && wl) { for (wchar_t *p = wt; *p; p++) if (*p == L'/') *p = L'\\'; DWORD attr = GetFileAttributesW(wt); if (attr == INVALID_FILE_ATTRIBUTES) { /* relative targets resolve against the link's directory */ wchar_t *slash = wcsrchr(wl, L'/'); if (!slash) slash = wcsrchr(wl, L'\\'); if (slash && wt[0] != L'\\' && !(wt[0] && wt[1] == L':')) { size_t dl = (size_t)(slash - wl) + 1, tl = wcslen(wt); wchar_t *full = malloc((dl + tl + 1) * sizeof(wchar_t)); if (full) { memcpy(full, wl, dl * sizeof(wchar_t)); memcpy(full + dl, wt, (tl + 1) * sizeof(wchar_t)); attr = GetFileAttributesW(full); free(full); } } } DWORD flags = 0x2; /* SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE */ if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)) flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; ok = CreateSymbolicLinkW(wl, wt, flags) != 0; } free(wt); free(wl); return ok ? 0 : -1; #else return symlink(target, link); #endif } /* rand_r replacement (same sequence everywhere). */ static CST_TEST_UNUSED int test_rand(unsigned *seed) { *seed = *seed * 1103515245u + 12345u; return (int)((*seed >> 16) & 0x7fff); } /* getline replacement: -1 at EOF. */ static CST_TEST_UNUSED long test_getline(char **line, size_t *cap, FILE *fp) { if (!*line || *cap == 0) { *cap = 256; *line = malloc(*cap); if (!*line) return -1; } size_t n = 0; int c; while ((c = fgetc(fp)) != EOF) { if (n + 2 > *cap) { size_t nc = *cap * 2; char *nl = realloc(*line, nc); if (!nl) return -1; *line = nl; *cap = nc; } (*line)[n++] = (char)c; if (c == '\n') break; } if (n == 0) return -1; (*line)[n] = 0; return (long)n; } static CST_TEST_UNUSED void test_sleep_ms(int ms) { #ifdef _WIN32 Sleep((DWORD)ms); #else poll(NULL, 0, ms); /* usleep is hidden by -std=c11 on musl */ #endif } #ifdef CST_TEST_SOCKETS #ifdef _WIN32 typedef SOCKET test_sock_t; #define TEST_BAD_SOCK INVALID_SOCKET #define test_sock_close closesocket #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif static CST_TEST_UNUSED void test_sock_init(void) { WSADATA w; WSAStartup(MAKEWORD(2, 2), &w); } #else #include #include #include #include typedef int test_sock_t; #define TEST_BAD_SOCK (-1) #define test_sock_close close static CST_TEST_UNUSED void test_sock_init(void) { signal(SIGPIPE, SIG_IGN); } #endif #endif /* CST_TEST_SOCKETS */ #endif